// ==UserScript==
// @name         115优化大师
// @author       zxf10608
// @version      8.5
// @icon      	 https://115.com/favicon.ico
// @namespace    https://greasyfork.org/zh-CN/scripts/408466
// @description  优化115网盘使用体验：一键离线下载、批量离线、调用Dplayer或Potplayer播放视频、文件快捷下载、批量下载、磁力转换种子等。
// @require      https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js
// @require      https://cdn.jsdelivr.net/npm/node-forge@1.3.1/dist/forge.min.js
// @require      https://cdn.jsdelivr.net/npm/big-integer@1.6.52/BigInteger.min.js
// @require      https://cdn.jsdelivr.net/npm/toastr@2.1.4/toastr.min.js
// @resource     toastrCss   https://cdn.jsdelivr.net/npm/toastr@2.1.4/build/toastr.min.css
// @require      https://cdn.jsdelivr.net/npm/localforage@1.10.0/dist/localforage.min.js
// @require      https://cdn.jsdelivr.net/npm/mp4box@0.5.4/dist/mp4box.all.min.js
// @require      https://cdn.jsdelivr.net/npm/mux.js@7.1.0/dist/mux.min.js
// @require      https://cdn.jsdelivr.net/npm/opencc-js@1.0.5/dist/umd/t2cn.js
// @require      https://cdn.jsdelivr.net/npm/hls.js@1.6.2/dist/hls.min.js
// @require      https://cdn.jsdelivr.net/npm/dplayer@1.27.1/dist/DPlayer.min.js
// @require      https://cdn.jsdelivr.net/gh/zxf10608/JavaScript/lib/115decode_v1.5.js#sha256=NjVXwLFXT32ufcvzMMYMuVPXO31uAbryfwyisGt2ekM=
// @require      https://cdn.jsdelivr.net/gh/zxf10608/JavaScript/lib/GM_config_zh-CN.js#sha256=0C3ZUIc3bAZJ/D3kz0g5eIwmpff0zeeLgTXHGEcxx3g=
// @resource     dplayerCss  https://cdn.jsdelivr.net/gh/zxf10608/JavaScript/lib/DPlayer_v1.8.css#sha256=in8D2Ng+LTjp2cm6ir7VM7l5cdQRRQecXTQ8ou1PpC4=
// @downloadURL  https://gitee.com/zxf10608/JavaScript/raw/master/115优化大师.user.js
// @updateURL    https://gitee.com/zxf10608/JavaScript/raw/master/115优化大师.user.js

// @match        http*://*/*
// @match        http*://*.115.com/*
// @exclude      http*://*.115.com/bridge*
// @exclude      http*://*.115.com/*/static*
// @exclude      http*://115.com/static*
// @exclude      http*://115.com/web/lixian/master/*
// @exclude      http*://*.baidu.com/*
// @exclude      http*://*.iqiyi.com/*
// @exclude      http*://*.qq.com/*
// @exclude      http*://*.youku.com/*
// @exclude      http*://*.bilibili.com/
// @exclude      http*://*.pptv.com/*
// @exclude      http*://*.fun.tv/*
// @exclude      http*://*.sohu.com/*
// @exclude      http*://*.le.com/*
// @exclude      http*://*.tudou.com/*
// @exclude      http*://*.bilibili.com/*
// @exclude      http*://music.163.com/*
// @exclude      http*://github.com/*
// @exclude      http*://gitee.com/*
// @exclude      http*://btcache.me/*
// @exclude      http*://*.jd.com/*
// @exclude      http*://*.taobao.com/*
// @exclude      http*://*.tmall.com/*
// @exclude      http*://*.vip.com/*
// @exclude      http*://*.pinduoduo.com/*

// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        GM_download
// @grant        GM_openInTab
// @grant        GM_setClipboard
// @grant        GM_getResourceText
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @connect      115.com
// @connect      115vod.com
// @connect      aps.115.com
// @connect      cpats01.115.com
// @connect      webapi.115.com
// @connect      proapi.115.com
// @connect      dl.115cdn.net
// @connect      videotsgo.115.com
// @connect      cdnfhnfile.115cdn.net
// @connect      aliyuncs.com
// @connect      *
// @grant        unsafeWindow
// @grant        window.open
// @grant        window.close
// @run-at       document-start
// @compatible   chrome
// @license      GPL License
// ==/UserScript==
(function() {
	
    'use strict';
	const newVersion = 'v8.5';
	
	if (typeof GM_config == 'undefined'){
		alert('115优化大师：\n网络异常，相关库文件加载失败，脚本无法使用，请刷新网页重新加载！');
		return;
	} else {
		console.log('115优化大师：相关库文件加载成功！');
	};
	
	function config(){  
		var windowCss = '#Cfg .nav-tabs {margin: 20 2} #Cfg .config_var textarea{width: 310px; height: 50px;} #Cfg .inline {padding-bottom:0px;} #Cfg .config_header a:hover {color:#1e90ff;} #Cfg .config_var {margin-left: 10%;margin-right: 10%;} #Cfg input[type="checkbox"] {margin: 3px 3px 3px 0px;} #Cfg input[type="text"] {width: 53px;} #Cfg {background-color: lightblue;} #Cfg .reset_holder {float: left; position: relative; bottom: -1em;} #Cfg .saveclose_buttons {margin: .7em;} #Cfg .section_desc {font-size: 10pt;}';
		
		GM_registerMenuCommand('设置', opencfg);
		function opencfg(){ 
			GM_config.open();
		};
		
		GM_config.init(  
		{
			id: 'Cfg',
			title: GM_config.create('a', {
				   href: 'https://greasyfork.org/zh-CN/scripts/408466',
				   target: '_blank',
				   className: 'setTitle',
				   textContent: '115优化大师',
				   title: '作者：zxf10608 版本：'+newVersion+' 点击访问主页'
					}),
			isTabs: true,
			skin: 'tab',
			css: windowCss,
			frameStyle: 
			{
				height: '550px',
				width: '430px',
				zIndex:'2147483648',
			},
			fields:
			{
				file_Down: 
				{
					section: ['文件管理', '提高文件管理效率'],
					label: '启用文件快捷下载',
					labelPos: 'right',
					type: 'checkbox',
					default: true,
				},
				down_batch:
				{
					label: '启用文件批量下载',
					labelPos: 'right',
					type: 'checkbox',
					default: true,
				},
				down_five:
				{
					label: '选中5个内直接下载', 
					labelPos: 'right',
					type: 'checkbox',
					default: true,
				},
				show_sha:
				{
					label: '下载后保存校验码',
					labelPos: 'right',
					type: 'checkbox',
					default: false,
				},
				down_Agreement:
				{
					label: '启用https下载协议', 
					labelPos: 'right',
					type: 'checkbox',
					default: false,
				},
				extract_file:
				{
					label: '启用文件批量提取',
					labelPos: 'right',
					type: 'checkbox',
					default: true,
					line: 'start',
				},
				extract_save:
				{
					label: '提取模式', 
					labelPos: 'left',
					type: 'select',
					'options': ['复制','移动'],
					default: '复制',
				},
				extract_type:
				{
					label: '提取类型', 
					labelPos: 'left',
					type: 'select',
					'options': ['全部','文件','视频','音乐','图片','文档'],
					default: '文件',
					line: 'end',
				},
				reminder1:
				{
					label: '温馨提示',
					labelPos: 'right',
					type: 'button',
					click: function(){
					alert('1、批量下载文件少于5个，直接浏览器下载。大于5个，所有下载地址将复制到剪切板；\n2、“https”下载协议仅在“http”访问不可用时启用，一般情况不建议开启，否则可能造成下载失败。');
					}
				},
				offline_Down: 
				{
					section: ['离线升级', '升级离线下载功能'],
					label: '启用一键离线下载',
					labelPos: 'right',
					type: 'checkbox',
					default: true,
				},
				offline_result:
				{
					label: '任务添加后显示离线结果',
					labelPos: 'right',
					type: 'checkbox',
					default: true,
				},
				open_List:
				{
					label: '离线后自动打开任务列表',
					labelPos: 'right',
					type: 'checkbox',
					default: false,
				},
				open_search:
				{
					label: '离线成功后开启视频搜索',
					labelPos: 'right',
					type: 'checkbox',
					default: true,
					line: 'start',
				},
				search_result:
				{
					label: '显示视频搜索结果',
					labelPos: 'right',
					type: 'checkbox',
					default: true,
				},
				open_Popup:
				{
					label: '搜到视频自动播放',
					labelPos: 'right',
					type: 'checkbox',
					default: false,
					line: 'end',
				},
				fuzzy_find:
				{
					label: '启用下载地址模糊匹配',
					labelPos: 'right',
					type: 'checkbox',
					default: false,
				},
				diy_folder:
				{
					label: '自定义离线下载文件夹',
					labelPos: 'right',
					type: 'checkbox',
					default: false,
					line: 'start',
				},
				save_folder:
				{
					label: '设置文件夹',
					labelPos: 'right',
					type: 'button',
					line: 'end',
					click: function(){
						setFolder();
					}
				},
				reminder2:
				{
					label: '温馨提示',
					labelPos: 'right',
					type: 'button',
					click: function(){
					alert('1、显示离线下载结果有10s延时，用于服务器响应时间。\n2、为避免通知弹窗过多，最多只显示3个视频搜索结果，更多请自行到115查看。\n3、“启用下载地址模糊匹配”后，能根据哈希值或纯文本模糊匹配磁力链接和迅雷专用链，如在磁力搜索引擎、资源网等有奇效，但在某些网页有一定几率误识别，请谨慎开启。');
					}
				},
				player:  
				{
					section: ['播放优化', '调用第三方播放器，优化播放体验'],
					label: '默认播放器', 
					labelPos: 'left',
					type: 'select',
					options: ['Dplayer','官方HTML5','本地播放','苹果IINA','其他'],
					default: 'Dplayer',
				},
				play_Quality:
				{
					label: '默认播放清晰度', 
					labelPos: 'left',
					type: 'select',
					'options': ['原码','最高','次高','最低'],
					default: '最高',
				},
				local_player:
				{
					label: '自定义本地播放接口',
					labelPos: 'right',
					type: 'button',
					click: function(){
						setLocalplayer();
					}
				},
				show_English:
				{
					label: '中英字幕保留英文',
					labelPos: 'right',
					type: 'checkbox',
					default: false,
				},
				M3U_list:
				{
					label: '启用生成播放列表',
					labelPos: 'right',
					type: 'checkbox',
					default: true,
				},
				Tab_ing:
				{
					label: 'DPlayer禁止后台播放',
					labelPos: 'right',
					type: 'checkbox',
					default: false,
				},
				web_fullscreen:
				{
					label: 'HTML5自动网页全屏',
					labelPos: 'right',
					type: 'checkbox',
					default: true,
				},
				thumb_Preview:
				{
					label: 'HTML5启用缩略图预览',
					labelPos: 'right',
					type: 'checkbox',
					default: true,
				},
				hide_play:
				{
					label: '隐藏第三方播放器悬浮按钮',
					labelPos: 'right',
					type: 'checkbox',
					default: false,
				},
				reminder3:
				{
					label: '温馨提示',
					labelPos: 'right',
					type: 'button',
					click: function(){
					alert('1、除第一、第二项外，其他仅在启用Dplayer时有效。\n2、开启云端记忆播放，播放记录将自动上传至云端（115服务器），下次播放自动恢复上一次进度。\n3、播放界面右键可显示更多菜单，谨慎使用“删除”操作。；\n4、本地播放器(Potplayer等)需手动关联M3U文件(.m3u)，方可正常打开M3U播放列表。播放列表有效期几小时，失效请重新生成；\n5、播放器跟随页面变化，即页面后台则暂停,页面前台则播放，支持Dplayer和官方HTML5。\n6、关于播放器调用说明：\n 单击文件名：默认播放器；\n 双击除文件名外：官方HTML5；\n 单击“Dp播放”：Dplayer；\n 单击“Pot播放”：Potplayer；\n 非115页面：默认播放器；\n 其他：复制视频链接。');
					}
				},
				hide_sidebar:  
				{
					section: ['更多设置', '优化浏览体验'],
					label: '隐藏网盘侧边栏',
					labelPos: 'right',
					type: 'checkbox',
					default: false,
				},
				hide_watermark:
				{
					label: '隐藏网盘水印', 
					labelPos: 'right',
					type: 'checkbox',
					default: true,
				},
				add_dir:
				{
					label: '默认新建文件夹', 
					labelPos: 'right',
					type: 'checkbox',
					default: true,
				},
				show_Alidity:
				{
					label: '显示上次登录时间', 
					labelPos: 'right',
					type: 'checkbox',
					default: false,
				},
				show_Star:
				{
					label: '网盘顶部增加星标按钮',
					labelPos: 'right',
					type: 'checkbox',
					default: false,
				},
				show_Offline:
				{
					label: '网盘顶部增加离线进度按钮',
					labelPos: 'right',
					type: 'checkbox',
					default: true,
				},
				show_Task:
				{
					label: '网盘顶部增加链接任务按钮',
					labelPos: 'right',
					type: 'checkbox',
					default: true,
				},
				show_Update:
				{
					label: '更新后弹出更新日志',
					labelPos: 'right',
					type: 'checkbox',
					default: false,
				},
				toastr:
				{				
					label: '通知弹出位置',
					labelPos: 'left',
					type: 'select',
					'options': ['左上', '右上', '中上','全铺'],
					default: '右上',
				},
				http_ua: 
				{
					label: '数据请求UA标识（非必要勿改）',
					type: 'textarea',
					default: ''
				},
				
				
			},
			
			events:
			{
				save: function(){
					GM_config.close();
					location.reload();
				}
			},
		});
	};
	config();

	const G = GM_config;
	const localHref = window.location.href;
	const show_result = G.get('offline_result');
	const down_reg = /^(magnet|thunder|ftp|ed2k):/i;
	const player_api = GM_getValue('localplayer') || 'potplayer://';
	const UA = G.get('http_ua')!=''? G.get('http_ua'):navigator.userAgent;
	const sign_url = 'https://115.com/?ct=offline&ac=space';
	const task_del = 'https://115.com/web/lixian/?ct=lixian&ac=task_del';
	const add_url = 'https://115.com/web/lixian/?ct=lixian&ac=add_task_url';
	const add_urls = 'https://115.com/web/lixian/?ct=lixian&ac=add_task_urls';
	const add_bt = 'https://115.com/web/lixian/?ct=lixian&ac=add_task_bt';
	const lists_url = 'http://115.com/web/lixian/?ct=lixian&ac=task_lists';
	const a_list= `<br><a target="_blank" class="openList" href="javascript:void(0);" style="color:blue;" title="打开离线链接任务列表">任务列表</a>`;
	console.log('115脚本UA：'+UA);
	
	function notice(){
		GM_addStyle(GM_getResourceText('toastrCss'));
		if(G.get('toastr')=='全铺'|| localHref.indexOf('https://captchaapi.115.com') != -1) {
			GM_addStyle('.toast{font-size:15px!important;} .toast-title{font-size:16px!important;text-align:center}');
		}else{
			GM_addStyle('.toast{font-size:15px!important;width:360px!important;} .toast-title{font-size:16px!important;text-align:center}');
		};
		var place = {'左上':'toast-top-left','右上':'toast-top-right','中上':'toast-top-center'}[G.get('toastr')] || 'toast-top-full-width';
		toastr.options = { 
			"closeButton": true, 
			"debug": false, 
			"progressBar": true, 
			"timeOut": 8000, 
			"extendedTimeOut": 8000, 
			"positionClass": place,
			"allowHtml": true,
			"newestOnTop" : false, 
			"preventDuplicates": true,

		};
	};
	notice();

	const requestQueue = {};
	const REQUEST_INTERVAL = 1000; 
	function AjaxCall(href, callback) {

		if (requestQueue[href]) {
			console.log(`请求被节流，等待前一个请求完成:\n ${href} `);
			return;
		}

		requestQueue[href] = true;
		
		GM_xmlhttpRequest({
			method: "GET",
			url: href,
			headers: {
				"User-Agent": UA,
				Origin: "https://115.com",
			},
			onload: function(data, status) {

				setTimeout(() => {
					delete requestQueue[href];
				}, REQUEST_INTERVAL);
				
				if(data.readyState == 4 && data.status == 200) {
					var htmlTxt = data.responseText;
					callback(null, htmlTxt);
				}
			},
			onerror: function(error) {

				setTimeout(() => {
					delete requestQueue[href];
				}, REQUEST_INTERVAL);
				callback(error);
			},
			ontimeout: function(error) {

				setTimeout(() => {
					delete requestQueue[href];
				}, REQUEST_INTERVAL);
				callback(error);
			}
		});
	}

	function setFolder(){
		var old_cid = GM_getValue('offlineFolder') || '';
		var new_cid = prompt('请输入离线下载保存文件夹的cid值：\n   ※ 获取cid值方法：打开需要保存到的网盘文件夹，复制地址栏中"cid="后面的一串数字，以"&"截止，如https://115.com/?cid=012345678912345678&...，cid值则为 012345678912345678。该项不填或填无效值则保存至默认文件夹（云下载）。※'
					  ,old_cid);
		if (/^(\d{17,19}|0)$/.test(new_cid)){
			GM_setValue('offlineFolder',new_cid);
			alert('设置成功，现cid值为：\n'+new_cid);
		}else if(new_cid==''){
			GM_setValue('offlineFolder','');
			alert('未输入cid值，保存至默认文件夹（云下载）。');
		}else if(new_cid==null){
			console.log('已点击取消');
		}else{
			alert('设置失败，cid值无效，请重新输入！\n（该值除根目录为 0 外，其他文件夹均为17至19位纯数字）');
			setFolder();
		};
	};
	
	function setLocalplayer(){
		var old_api = GM_getValue('localplayer') || 'potplayer://';	
		var new_api = prompt('请输入本地播放接口的api值：\n   ※ 默认值（potplayer://）为Potplayer播放器调用接口，可根据实际情况更改为其他本地播放器接口，需自行注册关联协议。※'
					  ,old_api);
		if (/^\w+:\/\/$/.test(new_api)){
			GM_setValue('localplayer',new_api);
			alert('设置成功，现api值为：\n'+new_api);
		}else if(new_api==''){
			GM_setValue('localplayer','potplayer://');
			alert('未输入api值，设为默认值（potplayer://）。');
		}else if(new_api==null){
			console.log('已点击取消');
		}else{
			alert('设置失败，api值无效，请重新输入！');
			setLocalplayer();
		};
	};
	
	function download(key,num){
		return new Promise(function(resolve,reject){
			var batch= typeof num != 'undefined'? true:false;
			var href = `https://webapi.115.com/files/download?pickcode=${key.pc}&_=${new Date().getTime()}`;
			AjaxCall(href,function(error,htmlTxt){
				if(error){
					console.log('网络错误，获取文件地址失败！');
					resolve([false,error]);
					return;
				};
				var json = JSON.parse(htmlTxt);

				if(json.state){
					var link=json.file_url;
					if (G.get('down_Agreement')){
						link=link.replace(/^http/,'https');
					};
					batch? resolve([link,key.n,num,key.sha]):resolve([link]);

				}else{

					originLink(key.pc,key.fid,'115origin').then(function(origin){
						if(origin[0]){
							var link=origin[0];
							if (G.get('down_Agreement')){
								link=link.replace(/^http/,'https');
							};
							batch? resolve([link,key.n,num,key.sha]):resolve([link]);
						}else{
							batch? resolve([false,key.n,num,origin[1]]):resolve([false,origin[1]]);
						};
					});
				};
			});
		});
	};

	function originLink(pid,fid,type,name,i){
		return new Promise(function(resolve,reject){

			if (type !='115origin' && G.get('play_Quality') !='原码'){
				console.log('未开启原码播放！');
				resolve([false,'ignore']);
				return;
			};
			var href = `https://proapi.115.com/app/chrome/downurl?t=${new Date().getTime()}`;
			var key = 'data='+m115_encode('{"pickcode":"'+pid+'"}');
			offline.getData(href,key).then(function(json){

				if(json.state){
					var txt=m115_decode(json.data)

					var data = JSON.parse(txt);
					var link = data[fid].url.url;

					name? resolve([link,name,i]):resolve([link]);
				}else{
					if(name){
						resolve([false,name,i,json.msg]);
					}else{
						resolve([false,json.msg]);
					};
				};
			});
		});
	};
	
	function getHistory(pid){
		return new Promise(function(resolve,reject){
			var href = 'https://webapi.115.com/files/history?pick_code='+pid+'&fetch=one&category=1';
			AjaxCall(href,function(error,htmlTxt){
				var time = 0;
				if(error){
					console.log('网络错误，获取播放记录失败！');
					resolve(time);
				};
				var json = JSON.parse(htmlTxt);
				if(json.state){
					if(!json.data.watch_end){
						time = json.data.time;
					};
				};
				resolve(time);
			});
		});
	};
	
	function getM3u8(video,type){
		return new Promise(function(resolve,reject){
			var herfLink = 'https://115.com/api/video/m3u8/'+video.pid+'.m3u8';

			AjaxCall(herfLink,function(error,htmlTxt){
				if (typeof htmlTxt == 'undefined') {
					transcoding(video,type);
					resolve([false,video]);
					return;
				};
				var dataList = htmlTxt.split('\n');
				var m3u8 = [];
				var temp = '"YH"|原画|"BD"|4K|"UD"|1080P|"HD"|超清|"SD"|高清|"3G"|标清';
				var txt = temp.split('|');
				for (var i=0; i<6; i++){
					dataList.forEach(function (e,j,arr) {
						if (e.indexOf(txt[i*2])!= -1) {
							m3u8.push({name: txt[i*2+1], url: arr[j+1].replace(/\r/g,''), type:'hls'});
						};
					});
				};

				if (m3u8.length ==1 || G.get('play_Quality') =='最高' || G.get('play_Quality') =='原码'){
					var num = 0;
				}else if(m3u8.length >1 && G.get('play_Quality') =='次高'){
					var num = 1;
				}else{
					var num = m3u8.length - 1;
				};
				video['quality'] = num;
				if(/\#EXT-X-MEDIA/.test(htmlTxt)){
					video['mutiSound'] = true;
					video['name']=video.name+'&nbsp;多音轨';
					}else{
					video['mutiSound'] = false;
				};
				resolve([m3u8,video,num]);
			});
		});
	};
	
	function switchPlayer(origin,type,m3u8,video){
		if(origin[0]){
			var link=origin[0];
			var name=origin[1];
			var definition='原码';
		}else if(m3u8){
			var link=m3u8.url;
			var name=video.name;
			var definition=m3u8.name;
		}else{
			toastr.error('未知错误，请稍后再试。','播放失败!');
			return;
		};
		var txt='';
		if((G.get('player') == 'Dplayer' && /^115/.test(type)) || type == 'Dp'){
			if(origin[0]) var txt='Dplayer不支持原码播放，';
		};

		if(type !='115origin' && G.get('play_Quality') !='原码' && video.mutiSound){
			var videoTxt = JSON.stringify(video);
			var player= G.get('player')=='本地播放'? G.get('player') : G.get('player')+'播放';
			var txt2 = `该视频为多音轨流媒体，转码后声音与画面已分离，当前条件下播放则无声音。<br>请更换以下方式即可正常播放视频。`;
			var h0 = `<br><a target="_blank" class="Dp" data=${videoTxt} href="javascript:void(0);" style="cursor:pointer;color:blue;" title="使用Dplayer播放该视频">Dplayer播放</a>`;
			var h1 = `&nbsp;&nbsp;<a target="_blank" class="HTML5play" href="https://115vod.com/?hls=1&pickcode=${video.pid}" style="cursor:pointer;color:blue;" title="使用官方HTML5播放该视频">HTML5播放</a>`;
			var h2 = `&nbsp;&nbsp;<a target="_blank" class="115origin" data=${videoTxt} href="javascript:void(0);" style="cursor:pointer;color:blue;" title="播放“原码”视频">原码播放</a>`;
			var h3 = `&nbsp;&nbsp;<a target="_blank" class="115down" data=${videoTxt} one="1" href="javascript:void(0);" style="cursor:pointer;color:blue;" title="下载该视频">下载</a>`;
			
			toastr.warning(txt2+h0+h1+h2+h3,`${player} 异常`);
			return;
		};
		if ((G.get('player') == '本地播放' && /^115/.test(type)) || type == 'Local'){
			if(/smplayer|potplayer2/.test(player_api)){
				var info=`${player_api}|${link}|${UA}|${name}`;
			}else{
				var info= player_api+link;
			};
			window.location.href = info;
		}else if((G.get('player') == '苹果IINA' && /^115/.test(type))){
			window.location.href = 'iina://weblink?url='+link;

		}else{
			GM_setClipboard(link);
			toastr.success(txt+'请使用其他播放器打开该地址。播放器User Agent必须与脚本UA一致！',`<span style="color:purple;">${definition}</span> 地址复制成功！`,{timeOut:8000});
		};
	};
	
	function playData(video,type){
		if ((G.get('player') =='官方HTML5' && type == '115play') || type == 'dblclick'){
			var link = 'https://115vod.com/?hls=1&pickcode='+video.pid; 
			GM_openInTab(link,false);
			return;
		}else if(G.get('play_Quality') !='原码'&&((G.get('player') == 'Dplayer'&& type == '115play') || type == 'Dp')){
			GM_setValue('currentVideoPid',video.pid);
			GM_openInTab('https://115.com/web/lixian/',false);
			return;
		};
		originLink(video.pid,video.fid,type,video.name).then(function(origin){
			if(origin[0]){
				switchPlayer(origin,type);
				return;
			}else if(origin[1]!='ignore'){
				toastr.warning('获取视频原码地址失败，将播放转码视频。','播放原码失败!',{timeOut:6000});
			};
			
			getM3u8(video).then(function(data){
				if(!data[0]){ 
					toastr.warning('未获取播放地址。','播放失败！');
					return;
				};
				switchPlayer(origin,type,data[0][data[2]],data[1]);
			}); 
		}, function(error) {
			toastr.error('服务器繁忙，请稍后再试。','操作异常!');
			console.log(error);
		});
	};
	
	function transcoding(video,type){

		var href = 'http://transcode.115.com/api/1.0/web/1.0/trans_code/check_transcode_job?sha1='+video.sha+'&priority=100';
		console.log('转码进度地址:'+href);
		AjaxCall(href,function(error,htmlTxt){
			var json = JSON.parse(htmlTxt);
			if(json.status == 1 || json.status == 3){
				var num = json.count;
				var time = tranTime(json.time).replace(/分.*/,'分');

				var txt = `等待转码排名：第${num}名，耗时：约${time}，请稍后再试或选择原码播放。`;
			}else if(json.status == 127){
				var txt = '未获取到转码进度，请稍后再试或选择原码播放。';	
				console.log('查询转码进度失败');
			};
			var videoTxt = JSON.stringify(video);
			var h1 = `<br><a target="_blank" class="transcode_show" data=${videoTxt} href="javascript:void(0);" style="cursor:pointer;color:blue;" title="打开转码进度详情页">转码详情</a>`;
			var h2 = '';
			var h3 = `&nbsp;&nbsp;<a target="_blank" class="115origin" data=${videoTxt} href="javascript:void(0);" style="cursor:pointer;color:blue;" title="播放“原码”视频">原码播放</a>`;
			
			if(type==3){
				alert(`播放失败，视频未转码，${txt}`);
				return;
			}else if(type==2){
				return;
			}else if(type==1){
				var title ='加速转码成功！';
			}else if(type){
				var title ='加速转码失败！';
				var txt = type;
			}else{
				var title ='播放失败，视频未转码！';
				h2 = `&nbsp;&nbsp;<a target="_blank" class="transcode_fast" data=${videoTxt} href="javascript:void(0);" style="cursor:pointer;color:blue;" title="加速转码进度">加速转码</a>`;
			};
			
			toastr.warning(txt+h1+h2+h3,title,{timeOut:10000});
		});
	};
	
	function transcod_fast(video){
		var push_url = 'https://115.com/?ct=play&ac=push';
		var key = `op=vip_push&pickcode=${video.pid}&sha1=${video.sha}`;
		offline.getData(push_url,key).then(function(json){
			console.log('加速转码结果:');
			console.log(json);
		
			if(json.state){
				var type= 1;
				transcoding(video,type);
				console.log('加速转码成功！');
				return;
			}else{
				var type= json.msg;
				transcoding(video,type);
				console.log('加速转码失败！');
			};
		});
	};
	
	function folderList(cid,name,type,sort,end){
		return new Promise(function(resolve,reject){
			var e = end? end:'';
			var s = sort? sort:'file_name';
			var key = {
				aid: 1,
				cid: cid,
				offset: 0,
				limit: 100,
				show_dir: 1,
				qid: 0,
				type: type,
				format: 'json',
				r_all: 1,
				o: s,
				suffix: e,
				asc: 1,
				cur: 1,
				natsort: 1
			};

			var href = 'https://aps.115.com/natsort/files.php?'+$.param(key);

			var tp = {'4':'视频','3':'音乐','2':'图片','1':'文档'}[type] || '文件';
			AjaxCall(href,function(error,htmlTxt){
				var json = JSON.parse(htmlTxt);

				if(json.state){
					if(json.count==0){
						toastr.warning(`文件夹：<span style="color:purple;">${name}</span> 未搜索到${tp}。`,'操作失败！');
						resolve([false,json]);
					}else if(json.count>99){
						toastr.warning('所属文件数量大于 <span style="color:red;">100</span> 个。','操作无效！');
						resolve([false,json]);
					}else{
						resolve([json,name]);	
					};
				}else{
					if(!end==4) toastr.warning(json.error,'文件夹检索失败!');
					resolve([false,json.error]);
				};
			});
		});
	};
	
	function batchList(key, down) {
        const isSingleVideo = (key.count == 1 && !down);
        const urls = [];
        const promises = [];

        function handleUrlResult(data, index) {
            if (!data[0]) {
                toastr.warning(data[1] + ' 获取地址失败!');
                console.log(data[1] + '获取地址失败：' + (data[3] || ''));
                return false;
            }
            return true;
        }

        for (let i = 0; i < key.count; i++) {
            const el = key.data[i];
            
            if (down) {
                promises.push(
                    download(el, i).then(data => {
                        if (handleUrlResult(data, i)) {
                            urls.push({num: i, href: data[0], name: data[1], sha: data[3]});
                        }
                    })
                );
            } else {
                if (G.get('play_Quality') == '原码' || isSingleVideo) {
                    promises.push(
                        originLink(el.pc, el.fid, '115origin', el.n, i).then(origin => {
                            if (handleUrlResult(origin, i)) {
                                urls.push({
                                    num: isSingleVideo ? 0 : origin[2],
                                    name: origin[1],
                                    txt: `#EXTINF:-1 ,${origin[1]} 原码\r\n${origin[0]}\r\n`
                                });
                            }
                        })
                    );
                }

                if (G.get('play_Quality') != '原码' || isSingleVideo) {
                    promises.push(
                        getM3u8({pid: el.pc, name: el.n, n1: i}, 2).then(data => {
                            if (!data[0]) {
                                toastr.warning(data[1].name + ' 未获取到转码播放地址。');
                            } else {
                                const m3u8 = data[0];
                                if (isSingleVideo) {
                                    m3u8.forEach((item, j) => {
                                        urls.push({
                                            num: j + 1,
                                            name: data[1].name,
                                            txt: `#EXTINF:-1 ,${data[1].name} ${item.name}\r\n${item.url}\r\n`
                                        });
                                    });
                                } else {
                                    urls.push({
                                        num: data[1].n1,
                                        name: data[1].name,
                                        txt: `#EXTINF:-1 ,${data[1].name} ${m3u8[data[2]].name}\r\n${m3u8[data[2]].url}\r\n`
                                    });
                                }
                            }
                        })
                    );
                }
            }
		}

        Promise.all(promises).then(() => {
            processResults(urls, key.count, down, isSingleVideo);
        });

        setTimeout(() => {
            if (urls.length === 0) {
                toastr.warning('所选文件大小均超出115M上限，无法获取文件地址。', '批量操作失败!');
            } else if (urls.length !== key.count && !isSingleVideo) {
                toastr.warning('部分文件地址获取失败。', '批量操作异常!');
            }
        }, 7000);


		function processResults(urls, count, isDownload, isSingleVideo) {
			urls.sort((a, b) => a.num - b.num);

			if (isDownload) {
				const links = urls.map(item => item.href);
				const m5ds = urls.map(item => `${item.name}|${item.sha}`).join('\r\n');
				
				if (count <= 5 && G.get('down_five')) {
					urls.slice(0, 5).forEach((item, i) => {
						setTimeout(() => GM_openInTab(item.href), i * 1000);
					});
					return;
				}

				if (G.get('show_sha')) {
					GM_download({url: `data:text/plain;charset=utf-8,${encodeURIComponent('文件校验码(sha1):\r\n' + m5ds)}`, name: '校验码'});
					toastr.success('校验码文档已下载！');
				}

				GM_setClipboard(links.join('\r\n'));
				toastr.success(`${urls.length}个下载地址复制成功！`);
			} else {
				const hrefs = `#EXTM3U\r\n#EXTVLCOPT:http-user-agent=${UA}\r\n` + 
					urls.map(item => item.txt).join('');
				const title = urls[0].name.replace(/\.\w{2,4}$/, '').replace(/\./g, '_') + 
					(isSingleVideo ? '' : '等') + '.m3u';
				
				GM_download({
					url: `data:audio/mpegurl;charset=utf-8,${encodeURIComponent(hrefs)}`,
					name: title
				});
				toastr.success(`共 ${urls.length} 个${isSingleVideo ? '清晰度' : '视频'}，请用本地播放器打开，失效请重新生成。`, '播放列表已下载');
			}
		}
	};
	
	function extractList(p_id,info){
		var num = [];
		var fids = [];
		var m = G.get('extract_save')=='复制'? 'copy':'move';
		var t = {'全部':'0','文件':'99','视频':'4','音乐':'3','图片':'2','文档':'1'}[G.get('extract_type')];
		
		for(let i=0;i<info.length;i++){
			folderList(info[i].cid,info[i].n,t).then(function(obj){
				if(obj[0]){
					num.push(obj[0].count);
					for(var j=0;j<obj[0].count;j++){
						var fid=obj[0].data[j].fid || ''; 
						if (fid!=''){
							fids.push(fid);
						}else{
							fids.push(obj[0].data[j].cid);
						};
					};
				}else{
					num.push('0');
				};
			});
		};
		
		var end=false;
		var time6 = setInterval(function(){
			if(info.length==num.length || end){
				var h=eval(num.join('+'));
				if(h>200){
					toastr.warning('所选文件总数大于 <span style="color:red;">200</span> 个，请分批操作。','操作无效！');
					return;
				};
				var link ='https://webapi.115.com/files/' + m;
				var key = {pid:p_id};
				for(var k=0; k<fids.length; k++){
					key['fid['+k+']']=fids[k];
				};
				offline.getData(link,$.param(key)).then(function(json){

					if(json.state) {
						toastr.success(h+' 个文件批量提取成功！');
						setTimeout(function(){
							window.location.reload();
						},3000);
						
					}else{
						toastr.warning(json.error,'批量提取失败!');
					};
				});
				clearInterval(time6); 
			};
		}, 100);
		
		setTimeout(function(){
			if(info.length!=num.length){
				if(num.length!=0){
					toastr.warning('部分文件提取失败。','批量操作异常!');
					end=true;
				}else{
					toastr.warning('所有文件夹提取操作均失败','批量提取错误!');
					clearInterval(time6);
				};
			};
		},8000);
	};
	
	function change(number){
		var size = "";
		if(number < 1024 * 1024 * 1024){
			size = (number/(1024 * 1024)).toFixed(2) + "MB";
		}else{                                            
			size = (number/(1024 * 1024 * 1024)).toFixed(2) + "GB";
		};
		var sizeStr = size + "";                        
		var index = sizeStr.indexOf(".");               
		var dou = sizeStr.substr(index + 1 ,2)          
		if(dou == "00"){                                
			return sizeStr.substring(0, index) + sizeStr.substr(index + 3, 2)
		};
		return size;
	};
	
	function name2(txt){
		var newName=txt.replace(/\.(?!\w{2,4}$)/g,'_')
					   .replace(/\s/g,'&nbsp;');
		return newName;
	};
	
	function tranTime(num){
		var showTime = '';
		if (num > 3600) {showTime += ' '+parseInt(num/3600)+' 小时'; num = num%3600;}
		if (num > 60) {showTime += ' '+parseInt(num/60)+' 分'; num = num%60;}
		return showTime += ' '+parseInt(num)+' 秒';
	};

	function enterPiP(videoEl){
		if(document.pictureInPictureEnabled && !videoEl.disablePictureInPicture) {
			if (!document.pictureInPictureElement) {
				videoEl.requestPictureInPicture();
			}else{
				document.exitPictureInPicture();
			};
		}else{
			alert('浏览器不支持或已关闭画中画功能！');
		};
	};
	
	function clickOne(el,t){
		var time=t? t:5000;
		if (el.attr('clicked') == 1){

			return false;	
		}else{
			el.attr('clicked',1);
			el.css('opacity','0.2');
			setTimeout(function(){
				el.attr('clicked',0);
				el.css('opacity','0.7');

			},time);
			return true;
		};
	};
	
	function resultMark(el,type){
		if(el.length==0 || !show_result) return;
		
		var urls=[];
		var color={1:'#00CCFF',2:'#DA70D6',3:'#AEDD81',4:'#EB7347'}[type];
		for(var i=0;i<el.length;i++){
			urls.push(el[i].url);
			$('.115offline').each(function(){
				var link=$(this).data('href');
				var $al=$(this).prev();
				var m=$al.attr('marked');
				if ((el[i].url == link || el[i].url == decodeURIComponent(link)) && m != 3){
					$al.attr('marked',type).css('background-color',color);
					$al.find('[style]').removeAttr('style');
					return false;
				};
			});
		};
		return urls;
	};
	
	function repeat(link){
		var result=false;
		var A=link.slice(0,60);
		if($('.115offline').length==0) return result;
		$('.115offline').each(function(){
			var B=$(this).data('href').slice(0,60);
			if(A.toLowerCase() == B.toLowerCase()){
				result=true;

				return false;
			};
		});
		return result;
	};
	
	function searchTask(json,link){
		var dataEl = false;
		for(var i=0;i<json.tasks.length;i++){
			if (json.tasks[i].url == link || json.tasks[i].url == decodeURIComponent(link)){
				dataEl = json.tasks[i];
				break;
			};
		};
		return dataEl;
	};
	
	function verify(){
		var time = new Date().getTime();
		var w=335; 
		var h=500; 
		var t = (window.screen.availHeight-h)/2; 
		var l = (window.screen.availWidth-w)/2;  
		var link = 'https://captchaapi.115.com/?ac=security_code&type=web&cb=Close911_'+time;
		var a = confirm('立即打开验证账号弹窗？');
		if (a){
			var blocked = false;
			try{
				var wroxWin=window.open(link,'请验证账号','height='+h+',width='+w+',top='+t+',left='+l+',toolbar=no,menubar=no,scrollbars=no,resizable=no,location=no,status=no');
				if(wroxWin == null){
					blocked = true;
				};
			}catch(e){
				blocked = true;
			};
			if(blocked){
				alert('验证弹窗已被拦截，请允许本页面弹出式窗口！');
			};
		};
	};
	
	function getRightUrl(url){
        var newUrl = url;
		if(/^thunder/i.test(url)){
            var key = url.replace(/thunder:\/\//i,'');
			var temp = atob(key);
			newUrl = decodeURIComponent(temp.slice(2,-2));
		};
        if(/^magnet/i.test(newUrl)){
            var hash=newUrl.split('&')[0].substring(20) || newUrl.substring(20);
            if(hash.length==32){
                hash=base32To16(hash);
			};
			newUrl='magnet:?xt=urn:btih:' + hash;
        }else if(/^\/\//.test(url)){
            newUrl=location.protocol + url;
        }else if(/^\/(?!\/)/.test(url)){
            newUrl=location.protocol+'//'+location.host+ url;
        };
        return newUrl;
    };
	
	function base32To16(str){ 
        if(str.length % 8 !== 0 || /[0189]/.test(str)){
            return str;
        };
        str = str.toUpperCase();
        var bin =  "", newStr = "", i;
        for(i = 0;i < str.length;i++){
            var charCode=str.charCodeAt(i);
            if(charCode<65)charCode-=24;
            else charCode-=65;
            charCode='0000'+charCode.toString(2);
            charCode=charCode.substr(charCode.length-5);
            bin+=charCode;
        };
        for(i = 0;i < bin.length;i+=4){
            newStr += parseInt(bin.substring(i,i+4),2).toString(16);
        };
        return newStr;
    };
	
	function getAttribute(e){
		var data = [] ;
		$.each(e.attributes, function() {

			if(this.specified && this.value.length>30){
				data.push(this.value);
			};
		});
		if($(e).text().length>25) data.push($(e).text());
		return data;
	};
	
	function right_menu(){
		$('body').append(`
		<div class="115menu" style="width:97px;height:85px;z-index:9123456789;overflow:hidden;position:absolute;display:none;background-color:#D0D0D0">
			<ul style="padding:5px 7px;margin:0px;list-style:none;">
				<li><a href="javascript:;" class="right_menu00">全选</a></li>
				<li><a href="javascript:;" class="right_menu01">反选</a></li>
				<li><a href="javascript:;" class="right_menu02">复制所选</a></li>
				<li><a href="javascript:;" class="right_menu11">复制地址</a></li>
				<li><a href="javascript:;" class="right_menu12">种子下载1</a></li>
				<li><a href="javascript:;" class="right_menu13">种子下载2</a></li>
			</ul>
		</div>`);
		$('.115menu a').css({'line-height':'25px','text-decoration':'none','color':'#2C3E50','padding':'1px 5px','font-size':'16px','font-family':'arial'});
		$('.115menu a').hover(function(){
			$(this).css({'background-color':'#2777F8','color':'#FFF'});
		},function(){
			$(this).css({'background-color':'','color':'#2C3E50'});
		});
	};
	
	function list_menu(){
		setTimeout(function(){

			var $down=$('#js_float_content [val^="download"]',parent.document);
			$down.siblings('[class^="115"],[class="Dp"],[class="Local"]').remove();
			var $sed=$('li[file_type].selected');
			
			if($sed.length==1){
				var file = {};
				file['pid']=$sed.attr('pick_code');
				file['fid']=$sed.attr('file_id');
				file['sha']=$sed.attr('sha1') || '';
				file['size']=change($sed.attr('file_size'));
				file['cid']=$sed.attr('cate_id') || '';
				file['name']=name2($sed.attr('title'));
				var fileTxt = JSON.stringify(file);
				if($('#js_operate_box').is(':hidden')){
					if(G.get('file_Down')){
						$down.hide().eq(0).after(`<li class="115down" title="快捷下载文件" data=${fileTxt} style="display: list-item;"><a href="javascript:;"><i class="icon-operate"></i><span>快捷下载</span></a></li>`);
					};
					if($sed.is(':has(.duration):not([file_mode="4"])')){
						var player2=player_api.match(/([a-z]{2,4})(p|\d|:)/i)[1].toUpperCase();
						var cl = {'Dplayer':'Dp','本地播放':'Local'}[G.get('player')] || '115play';
						var txt0 = ['Local','Dp'];
						var txt1 = ['使用'+player2+'播放视频','使用Dplayer播放视频'];
						var txt2 = [player2+'播放','Dp播放'];
						for (var i=0; i<2; i++){
							$down.eq(0).after(`<li class="${txt0[i]}" title=${txt1[i]} data=${fileTxt} style="display: list-item;"><a href="javascript:;"><span>${txt2[i]}</span></a></li>`);
						};
					};
				};
			};	
			
			if($('#js_operate_box').is(':visible') &&G.get('file_Down') && $sed.length==1){
				$('#js_operate_box [menu="download"]').replaceWith(`<li class="115down" title="快捷下载文件" data=${fileTxt}><i class="icon-operate ifo-download"></i><span>快捷下载</span></li>`);
			};
			
			if($('li[file_type="0"].selected').length==0){
				if($sed.length>0){ 

					if(G.get('M3U_list')){
						if($('#js_operate_box').is(':hidden')){
							$down.siblings('.115down').addBack().eq('-1').after('<li class="115M3Ulist" title="生成M3U播放列表" style="display:list-item;"><a href="javascript:;"><i class="icon-operate"></i><span>播放列表</span></a></li>');
						}else if($('.115M3Ulist').length<1){
							$('#js_operate_box li:eq(0)').after('<li class="115M3Ulist" title="生成M3U播放列表"><i class="icon-operate"></i><span>播放列表</span></li>');
						};
					};
				};
				if($sed.length>1){

					if(G.get('down_batch') && navigator.userAgent.indexOf('115') == -1){
						if($('#js_operate_box').is(':hidden')){
							$down.hide().eq(0).after('<li class="115down_batch" title="批量下载文件" style="display:list-item;"><a href="javascript:;"><i class="icon-operate"></i><span>批量下载</span></a></li>');
						}else if($('.115down_batch').length<1){
							$('#js_operate_box [menu="download"]').replaceWith('<li class="115down_batch" title="批量下载文件"><i class="icon-operate ifo-download"></i><span>批量下载</span></li>');
						};
					};
				};
			};
			
			if(G.get('extract_file') && $sed.length>0 && $('li[file_type="1"].selected').length==0){
				var ifo = G.get('extract_save')=='复制'? 'ifo-copy':'ifo-move';
				if($('#js_operate_box').is(':hidden')){
					$down.siblings('[class^="115down"]').addBack().eq('-1').after(`<li class="115extract" title="批量提取文件夹子文件" style="display:list-item;"><a href="javascript:;"><i class="icon-operate ${ifo}"></i><span>批量提取</span></a></li>`);
				}else if($('.115extract').length<1){
					$('#js_operate_box [menu="download"],#js_operate_box [class^="115down"]').eq('-1').after(`<li class="115extract" title="批量提取文件夹子文件"><i class="icon-operate ${ifo}"></i><span>批量提取</span></li>`);
				};
			};

		}, 100);
	};
	
	function formatDate(date,type,format){
		if (type){
			let seconds;
			const timeParts = date.split(':');
			if(timeParts.length === 3) {
				seconds = parseInt(timeParts[0]) * 3600 + 
						parseInt(timeParts[1]) * 60 + 
						parseInt(timeParts[2]);
			} else if(timeParts.length === 2) {
				seconds = parseInt(timeParts[0]) * 60 + 
						parseInt(timeParts[1]);
			};
			return seconds;
			
		}else{
			if (!(date instanceof Date)) {
				date = new Date(date);
			};
			const o = {
				"M+": date.getMonth() + 1, 
				"d+": date.getDate(), 
				"H+": date.getHours(), 
				"m+": date.getMinutes(), 
				"s+": date.getSeconds(), 
				"q+": Math.floor((date.getMonth() + 3) / 3), 
				"S": date.getMilliseconds() 
			};
			if (/(y+)/.test(format)) {
				format = format.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
			};
			for (const k in o) {
				if (new RegExp("(" + k + ")").test(format)) {
					format = format.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
				}
			};
			return format;
		};
	};
	
	function offline_batch_message(results, total){
		var s = results.success.length, e = results.exist.length, f = results.fail.length;
		var error = f>0 ? results.fail[0].error_msg : '任务已存在';
		var txt2 = e>0 ? '新建任务10秒后显示离线结果,前2个显示搜索结果弹窗。' : '10秒后显示离线结果,前2个显示弹窗。';
		
		if(f+e == total) return {
			type: 'warning',
			text: `有 <span style="color:red;">${f+e}</span> 个任务创建失败，原因：${error}。`,
			title: '批量离线任务添加失败。'
		};
		if(f+e > 0) return {
			type: 'info',
			text: `有 <span style="color:purple;">${s}</span> 个任务创建成功。有 <span style="color:red;">${f+e}</span> 个任务创建失败，原因：${error}。${txt2}`,
			title: '批量离线任务添加成功。'
		};
		return {
			type: 'info',
			text: `有 <span style="color:purple;">${s}</span> 个任务创建成功。${txt2}`,
			title: '批量离线任务添加成功。'
		};
	};	
	
	$(document).ready(async () => {
		if(localHref.indexOf('https://115.com/') != -1) {

			if (typeof (unsafeWindow.USER_ID) != 'undefined') {
				GM_setValue('115ID', unsafeWindow.USER_ID);

					} else { 

					};

			if (G.get('hide_watermark')){
				setTimeout(function(){
					$('div[class^="fp-"]').hide();
				},2000);
				
			};

			if (G.get('add_dir')){
				$('#js_top_panel_box [menu="create_word"]').attr('menu','add_dir');
			};

			var $topEl = $('#js_top_panel_box [id="js_filter_btn"]').addClass('btn-line');
			if (G.get('show_Star')){
				$topEl.after('<a href="javascript:;" file_dialog_menu="star" class="button btn-line" id="js_star_list_btn"><i class="icon-operate ifo-fav"></i><span>星标</span></a>');
			};

			if (G.get('show_Task')){
				$topEl.after('<a href="javascript:;" class="button btn-line btn-upload" menu="offline_task"><i class="icon-operate ifo-linktask"></i><span>链接任务</span></a>');
			};

			if (G.get('show_Offline') && localHref.indexOf('https://115.com/?ct=file') != -1) {
				$topEl.after('<a href="javascript:;" class="button btn-line" tab_btn="wangpan" mode-tab="offline"><i class="icon-operate ifo-linktask"></i><span>离线进度</span></a>');

				$('body').append(`
				<script>
					$('body').on('click','#js_top_panel_box [mode-tab="offline"]',function(e){
						window.open('https://115.com/?tab=offline&mode=wangpan', '_blank');

						return false;
					});
				</script>`);
			};

			if (G.get('hide_sidebar')){
				$('.sub-hflow-file').hide();
				$('.wrap-vflow [rel="page_top_btn"]').show();
			};
			
			setTimeout(function(){
				$('.promptbar-caution,.feature-float,.ceiling-feature').remove();
			},2000);

			if (G.get('file_Down')){
				var herfd = 'li[file_type]:not([down_button="1"])';
				$('body').on('mouseenter',herfd,function(){
					var $El = $(this).attr('down_button',1);
					$El.find('.file-opr a[menu^="download"]').hide();
					$El.find('.file-opr').prepend('<a class="115down" href="javascript:;"title="快捷下载文件"><i class="icon-operate ifo-download"></i><span>快捷下载</span></a>');

				}); 
			};

			var herfv = 'li[file_type="1"]:has(.duration):not([file_mode="4"],[paly_button="1"])';
			var player2=player_api.match(/([a-z]{2,4})(p|\d|:)/i)[1].toUpperCase();
			$('body').on('mouseenter',herfv,function(){
				var $El = $(this).attr('paly_button',1);
				var cl = {'Dplayer':'Dp','本地播放':'Local'}[G.get('player')] || '115play';
				$El.find('.name').addClass(cl).removeAttr('menu');
				var txt0 = ['Local','Dp'];
				var txt1 = ['使用'+player2+'播放视频','使用Dplayer播放视频'];
				var txt2 = [player2+'播放','Dp播放'];
				if (!G.get('hide_play')){
					for (var i=0; i<2; i++){
						$El.find('.file-opr').prepend('<a href="javascript:;" class='+txt0[i]+' title='+txt1[i]+'><span>'+txt2[i]+'</span></a>');
					};
				};
				
				$El.not('.name').dblclick(function(){
					var type = 'dblclick';
					var pid1 = $El.attr('pick_code');
					var video = {'pid':pid1};
					playData(video,type);
					return false;
				});
			});

			$('body').on('mouseenter','li[rel="item"]:not([batch_button="1"])',function(e){
				var $El=$(this).attr('batch_button',1);
				$El.find('.checkbox').addBack().click(function(){
					list_menu();
				});
				$El.contextmenu(function(){
					list_menu();
				});
			});
			$('body').on('mouseenter','.list-header:not([batch_button="1"])',function(){
				var $El=$(this).attr('batch_button',1);
				$El.find('[menu="file_check_all"]').click(function(){
					list_menu();
				});

			});
			
		};
		
		if(localHref.match(/^https:\/\/115\.com\/web\/lixian\/$/) !== null)  {

			Xplayer.addDPlayer();
		};
		
		if(localHref.indexOf('https://115vod.com/') != -1) {
			$('.feature-float').remove();
			$('.bar-side ul').prepend(`
				<li><a href="javascript:;" class="openPiP" 
				style="float:left;width:40px;height:20px;margin:10px 5px;border-radius:3px;font-size:12px;text-align:center;background:#666;color:#fff;opacity:0.7;">
				<s>画中画</s><div class="tooltip" >开启画中画</div></a></li>`);
			if (G.get('web_fullscreen')){
				$('body').append(`<script>$('.bar-side [rel="web_fullscreen"]').click();</script>`);
			};
			
			if (G.get('thumb_Preview')){
				$('body').append(`<div id="thumbPreview" style="position:absolute;z-index:999;display:none;"></div>`);
				const pid1 = new URL(localHref).searchParams.get('pickcode');
				const video = {pid:pid1};
				const m3u8_Info = await getM3u8(video,2);
				if (m3u8_Info){
					console.log('m3u8母文件获取成功。');
				}else{
					console.log('m3u8母文件获取失败。');
					return false;
				};
				
				const data={
					pid:pid1,
					url:m3u8_Info[0][0].url,
					barEl:$('.bar-progress'),
					timeEl:$('.bar-preview'),
					type:'html5'
				};

				clipper.triggerPreview(data);
			};
		};
		
		if(localHref.indexOf('https://captchaapi.115.com') != -1 && window.top==window.self) {
			window.focus();
			toastr.info('验证成功后本页面将自动关闭.');
			var t_close;
			$('#js_ver_code_box [rel="verify"]').click(function (){
				t_close=setTimeout(function(){
					console.log('验证正确');
					window.open('','_self');
					window.close();
				}, 200);
				return false;
			});
			
			$('.vcode-hint').on('DOMNodeInserted',function(e){
				console.log('验证错误！');
				clearTimeout(t_close);
			});

		};
		
		if(localHref.indexOf('https://115.com/?cid=0&offset=0&mode=wangpan') != -1) {
			window.onload=function(){
				if (G.get('show_Alidity') && typeof unsafeWindow.USER_ID != 'undefined'){
					var login_info = 'http://passportapi.115.com/app/1.0/web/9.2/login_log/login_devices';
					AjaxCall(login_info,function(error,htmlTxt) {
						var json = JSON.parse(htmlTxt);
						if(json.state==1) {
							var time = json.data.last.utime;
							var date = new Date(time * 1000);
							var loginTime = formatDate(date,false,"yyyy年MM月dd日 HH:mm");
							toastr.success('上次登录时间：'+loginTime,{timeOut:5000});
							console.log('登录时间:\n'+loginTime);
						} else {
							var txt=json.error || '网络错误，未知时间！';
							toastr.warning('上次登录时间：'+txt,{timeOut:5000});
						};
					});
				};
		
			};
		};
		
		var oldVer = GM_getValue('version') || '';
		if (G.get('show_Update') && oldVer != newVersion){
			var txt=`115优化大师 ${newVersion} 更新日志：\n更新日期：2025年5月25日 \n1、跳过片头片尾功能移到Dplayer右下角的设置中。\n2、新增“隐藏网盘水印”功能，默认开启，可到设置中关闭。\n3、Dplayer增加选集、字幕设置、多音轨切换等功能。`
			setTimeout(function(){
				alert(txt);
			},2000);
			GM_setValue('version',newVersion);
		};
		
	});

	const clipper = function(){

		const storage = localforage.createInstance({
			name: '115optimize',
			storeName: 'clipper',
			version: 1,
			description: "缩略图缓存数据",
			driver: localforage.INDEXEDDB
		});
		storage.keys().then(keys => {
			const now = Date.now();
			return Promise.all(
				keys.filter(k => k.startsWith('video_'))
					.map(k => storage.getItem(k)
						.then(data => {
							if(data && now - data.lastTime > 604800000) {
								return storage.removeItem(k);
							}
						})
					)
			);
		}).catch(err => {
			console.error('清理缩略图缓存失败:', err);
		});
		
		localforage.dropInstance({
		  name: "115Preview"
		}).then(function() {
		  console.log('历史遗留115Preview数据库已被清除');
		});
		return {

			fetch:function(url,type){
				let rt;
				let ra;
				switch(type) {
				  case 1:
					rt = 'text';
					ra = 'bytes=0-';
					break;
				  case 2:
					rt = 'arraybuffer';
					ra = 'bytes=0-262144';
					break;
				  case 3:
					rt = 'arraybuffer';
					ra = 'bytes=0-';
					break;
				  default:
					rt = 'text';
					ra = 'bytes=0-';
				}
				return new Promise((resolve, reject) => {
					GM_xmlhttpRequest({
						method: "GET",
						url: url,
						responseType: rt,
						headers: {
							"Range": ra,
							"User-Agent": UA,
							"Origin": "https://115.com"
						},
						onload: function(data) {
							if(data.readyState === 4 && /^2/.test(data.status) && type==1) {
								var htmlTxt = data.responseText;
								resolve(htmlTxt);
							} else if(data.readyState === 4 && /^2/.test(data.status) && type!=1) {

								if(!(data.response instanceof ArrayBuffer)) {
									reject(new Error('服务器返回了无效的ArrayBuffer数据'));
									return;
								};
								resolve(data.response);
							} else {
								reject(new Error(`请求失败: ${data.status}`));
							};
						},
						onerror: function(error) {
							reject(new Error(error));
						},
						ontimeout: function(error) {
							reject(new Error('请求超时'));
						}
					});
				});
			},

			getExtradata:function(mp4box){
				try {
					const entry = mp4box.moov.traks[0].mdia.minf.stbl.stsd.entries[0];
					const box2 = entry.avcC ?? entry.hvcC ?? entry.vpcC;
					if (box2 != null) {
						const buffer = new ArrayBuffer(1024);
						const stream = new DataStream(buffer, 0, DataStream.BIG_ENDIAN);
						box2.write(stream);

						return new Uint8Array(stream.buffer, 8, stream.position - 8);
					}
				} catch (error) {
					console.error("获取解码器所需的额外数据出错：", error);
				}
				return null;
			},

			calcClipSize:function(width, height, maxWidth, maxHeight){
				const scale = Math.min(maxWidth / width, maxHeight / height);
				return {
					width: width * scale,
					height: height * scale
				};
			},

			createDecodePipeline:function({nbSamples, maxWidth, maxHeight}){
                const frames = [];
                const mp4boxfile = MP4Box.createFile(); 
                const transmuxer = new muxjs.mp4.Transmuxer({ 
                    keepOriginalTimestamps: true 
                });
                let videoDecoder = null; 
                let videoTrack = null;  
                let currentPosition = 0; 
                let sampleCount = 0;    

                function handleError(error, reject) {
                    console.error('处理错误:', error);
                    reject(error);
                }

                const promise = new Promise((resolve, reject) => {

                    transmuxer.on("data", (segment) => {
                        try {

                            const buffer = new Uint8Array([
                                ...new Uint8Array(segment.initSegment),
                                ...new Uint8Array(segment.data)
                            ]).buffer;
                            buffer.fileStart = currentPosition; 
                            mp4boxfile.appendBuffer(buffer); 
                            mp4boxfile.flush(); 
                        } catch (error) {
                            handleError(error, reject);
                        }
                    });

                    transmuxer.on("error", (error) => handleError(error, reject));

                    mp4boxfile.onReady = (info) => {
                        videoTrack = info.videoTracks[0]; 
                        if (!videoTrack) return;

                        mp4boxfile.setExtractionOptions(videoTrack.id, "video", {nbSamples});

                        const {width, height} = clipper.calcClipSize(
                            videoTrack.track_width,
                            videoTrack.track_height,
                            maxWidth,
                            maxHeight
                        );

                        videoDecoder = new VideoDecoder({

                            output: async (videoFrame) => {

                                const img = await createImageBitmap(videoFrame, {
                                    resizeQuality: "pixelated",
                                    premultiplyAlpha: "none",
                                    resizeWidth: width,
                                    resizeHeight: height
                                });

                                frames.push({
                                    img,
                                    duration: videoFrame.duration,
                                    timestamp: videoFrame.timestamp
                                });
                                videoFrame.close(); 

                                if (++sampleCount >= nbSamples) resolve(frames);
                            },
                            error: (error) => handleError(error, reject)
                        });

                        try {

                            videoDecoder.configure({
                                codec: videoTrack.codec, 
                                codedWidth: videoTrack.track_width, 
                                codedHeight: videoTrack.track_height, 
                                hardwareAcceleration: "prefer-hardware", 
                                optimizeForLatency: true, 
                                description: clipper.getExtradata(mp4boxfile) 
                            });
                            mp4boxfile.start(); 
                        } catch (error) {
                            handleError(error, reject);
                        }
                    };

                    mp4boxfile.onSamples = (trackId, _, samples) => {
                        if (videoTrack.id !== trackId) return; 
                        
                        mp4boxfile.stop(); 

                        samples.slice(0, nbSamples - sampleCount).forEach(sample => {

                            const chunk = new EncodedVideoChunk({
                                type: sample.is_sync ? "key" : "delta", 
                                timestamp: sample.cts * 1e7 / videoTrack.timescale, 
                                duration: sample.duration * 1e7 / videoTrack.timescale, 
                                data: sample.data 
                            });
                            videoDecoder.decode(chunk); 
                        });
                        videoDecoder.flush(); 
                    };
                });

                return {
                    mp4boxfile,    
                    transmuxer,    
                    videoDecoder,  
                    videoTrack,    
                    promise,      
                    push: (buffer, pos) => { 
                        transmuxer.push(buffer);
                        currentPosition = pos;
                    },
                    frames        
                };
            },

			processStreamingSegment:async function({options, url, nbSamples, maxWidth, maxHeight}) {

				const MAX_STEP_COUNT = 5; 
				const DEFAULT_CHUNK_SIZE = 256 * 1024; 
				const chunkSize = options.stepChunkSize || DEFAULT_CHUNK_SIZE;

				let step = 0;
				let buffers = [];
				let bufferSize = 0;

				const pipeline = clipper.createDecodePipeline({
					nbSamples,
					maxWidth,
					maxHeight
				});

				const processChunk = async () => {
					try {

						const buffer = await clipper.fetch(url, 2);
						buffers.push(new Uint8Array(buffer));
						bufferSize += buffer.byteLength;

						const mergedBuffer = new Uint8Array(bufferSize);
						let offset = 0;
						for (const buf of buffers) {
							mergedBuffer.set(buf, offset);
							offset += buf.byteLength;
						}

						pipeline.push(mergedBuffer, 0);
						pipeline.transmuxer.flush();

						if (pipeline.frames.length > 0) {
							return pipeline.frames;
						}

						const result = await Promise.race([
							pipeline.promise,
							new Promise(resolve => setTimeout(() => resolve('timeout'), 100))
						]);

						if ((result === 'timeout' || pipeline.frames.length === 0) && step++ < MAX_STEP_COUNT) {
							return processChunk(); 
						}
						
						return pipeline.frames; 
						
					} catch (error) {
						if (error.name === "EncodingError" && step++ < MAX_STEP_COUNT) {
							return processChunk(); 
						}
						throw error; 
					}
				};

				return processChunk(); 
			},

			getPidData:async function(pidKey){
				const now = Date.now();

			    let pidData = await storage.getItem(pidKey) || {
			        preloaded:false,
					lastTime: now,
			        m3u8Time: 0,
			        m3u8Info: [],
			        clipInfo: []
			    };
				return pidData;
			},

			fetchM3U8Info:async function(url,pid){
				const now = Date.now();
				const pidKey = `video_${pid}`;
				let pidData = await clipper.getPidData(pidKey);

				pidData.lastTime = now;

			    if (now - pidData.m3u8Time > 2 * 60 * 60 * 1000) {
			        await localforage.removeItem('m3u8Info');
			        pidData.m3u8Time = now;
			        pidData.m3u8Info = [];
			    };

			    if (pidData.m3u8Info.length > 0) {
					console.log('clipper:从缓存获取到m3u8信息。');
			        return pidData.m3u8Info;
			    };

				const htmlTxt = await clipper.fetch(url, 1);
			    const lines = htmlTxt.split('\n').filter(line => line.trim());
			    let startTime = 0;
			    pidData.m3u8Info = lines.map((line, index) => {
			        if (line.startsWith('#EXTINF:')) {
			            const duration = Number(line.match(/#EXTINF:([\d.]+)/)[1]);
						const url_temp = lines[index + 1].trim().replace(/`/g, '');
						const uri = new URL(url_temp, url).href;  
						const endTime = Number(startTime) + Number(duration);  
						const info = {  
							  _url:uri,	
							  _duration: duration,	
							  _startTime: startTime,
							  _endTime: endTime,	
							  _index: (index-5)/2	
						};
						startTime = endTime;
						return info;
					}
			    }).filter(Boolean);
			    
			    await storage.setItem(pidKey, pidData);
			    return pidData.m3u8Info;
			},

			findSegmentByTime:async function(time,pidKey){
				let pidData = await clipper.getPidData(pidKey);
			    if (pidData.m3u8Info.length == 0) {
					return false;
			    };
				
				for (const segment of pidData.m3u8Info) {
					if (time >= segment._startTime && time < segment._endTime) {
						return segment;
					};
				};
				return false;
			},

			getClip:async function(segment,pidKey,time,type,options){
				let pidData = await clipper.getPidData(pidKey);

				if (pidData.clipInfo.length > 0) {
					for (const clipData of pidData.clipInfo) {
						if (time >= clipData.startTime && time < clipData.endTime) {

							return clipData;
						};
					};
				};				

			    const segmentClips = await clipper.processStreamingSegment({
			        options: options,
			        url: segment._url,
			        nbSamples: options.nbSamples,
			        maxWidth: options.maxWidth,
			        maxHeight: options.maxHeight
			    });

			    if (segmentClips && segmentClips.length > 0) {
			        try {
						const blob = await clipper.imageBitmapToBlob(segmentClips[0].img);
						const frameData = {
							img:blob,
							width:segmentClips[0].img.width,
							height:segmentClips[0].img.height,
							locationTime:time,
			                startTime: segment._startTime,
							endTime:segment._endTime
			            };
			            
						if (type){

							pidData.clipInfo.push(frameData);
							await storage.setItem(pidKey, pidData);
						};

						return frameData;
						
			        } catch (error) {
						console.error('保存缩略图缓存失败:', error);
						return false;
			        }
			    };
				return false;
			},

			imageBitmapToBlob:async function(imageBitmap, quality = 1, type = 'image/jpeg'){
				const canvas = $('<canvas>')[0];
				canvas.width = imageBitmap.width;
				canvas.height = imageBitmap.height;
				const ctx = canvas.getContext('2d');
				ctx.drawImage(imageBitmap, 0, 0);
				
				return new Promise((resolve) => {
					canvas.toBlob((blob) => {
						resolve(blob);
					}, type, quality);
				});
			},

			preloadThumbnails:async function(pid,m3u8Info){
				const startTime = Date.now(); 
				const totalSegments = m3u8Info.length;
				const totalDuration = m3u8Info[totalSegments-1]._endTime;
				const startTime2 = formatDate(startTime,false,"HH:mm");
				console.log(startTime2 +' 开始缩略图预加载。');
				
				const pidKey = `video_${pid}`;
				let pidData = await clipper.getPidData(pidKey);
				if (pidData.preloaded) {
					console.log(`缩略图已预加载过，跳过重复加载。`);
					return;
				};

				let totalCount = 0;
				let successCount = 0;
				let failCount = 0;

				let timePoints = [];
				if(totalSegments <= 50) {

					timePoints = m3u8Info.map(seg => seg._startTime + seg._duration/2);
				} else {

					const interval = totalDuration / 50;
					for(let i = 0; i < 50; i++) {
						timePoints.push(i * interval);
					};
				};
				totalCount = timePoints.length; 

				let delay = 1000;
				for(const time of timePoints) {
					setTimeout(async () => {
						try {
							const segment = await this.findSegmentByTime(time, pidKey);
							if(segment) {
								const frameData = await this.getClip(segment,pidKey,time,false, {
									nbSamples: 1,
									maxWidth: 320,
									maxHeight: 180
								});
								if(frameData && frameData.img instanceof Blob) { 
									pidData.clipInfo.push(frameData);
								};
								successCount++;
							};
						} catch(e) {
							console.error('预加载缩略图失败:', e);
							failCount++;
						} finally {

							if(successCount + failCount === totalCount) {
								clipper.getPidData(pidKey).then(function(existingData) {
									pidData.preloaded = true;

									pidData = {
										...existingData,
										...pidData,
										clipInfo: [...(existingData.clipInfo || []), ...pidData.clipInfo]
									};

									storage.setItem(pidKey, pidData);
								});
								const endTime = Date.now(); 
								const totalTime = (endTime - startTime) / 1000; 
								console.log(`缩略图预加载完成: 总请求数=${totalCount}, 成功数=${successCount}, 失败数=${failCount}, 总耗时=${totalTime.toFixed(2)}秒。`);
							};
						}
					}, delay);
					delay += 1000;
				};
			},

			getPreview:async function(pid,time,type){
				const pidKey = `video_${pid}`;
				const segment = await clipper.findSegmentByTime(time,pidKey);
				if (!segment) {
					console.log(time+'s 视频片段获取失败。');
					return false;
				};
				const thumb = await clipper.getClip(segment,pidKey,time,true,{
					nbSamples: 1,
					maxWidth: 320*1.2,
					maxHeight: 180*1.2
				});
				
				if(thumb && thumb.img instanceof Blob) { 
					let left=0;
					let top=0;
					let time_left=0;
					let time_top=0;

					if(type=='dplayer') {
						time_left = $('.dplayer-bar-time').offset().left;
						left = time_left - thumb.width/2 +19;
						top = $('.dplayer-bar-time').offset().top - thumb.height - 8;
					}else{
						time_left = $('.bar-progress [rel="time_text"]').offset().left;
						left = time_left - thumb.width/2 +15;
						const progressRect = $('.progress-loaded')[0].getBoundingClientRect();
						top = progressRect.top - thumb.height - 35; 
					};
					if(left < 0) {
						left = time_left - 20; 
					} else if(left + thumb.width > window.innerWidth) {
						left = time_left - thumb.width + 20; 
					};
					
					const imgUrl = URL.createObjectURL(thumb.img);
					$('#thumbPreview').html('').append(`<img src="${imgUrl}">`)
						.css({
							left: left+ 'px',
							top: top+ 'px',
							display: 'block'
					});
					console.log(time+'s 缩略图获取成功。');
					return imgUrl;
				};
				return false;
			},

			triggerPreview:async function(data){
				try {
					const m3u8Info = await clipper.fetchM3U8Info(data.url,data.pid);
					if (!m3u8Info){
						console.log('clipper：m3u8信息获取失败，缩略图预览不可用。');
						return;
					};

					setTimeout(() => {
						clipper.preloadThumbnails(data.pid,m3u8Info);
					}, 600000);
					
					let hoverTimer;
					let leave = true;
					let lastTime = 0;
					let ThumbUrl = false;
					let firstTrigger = true;

					data.barEl.off('mouseenter mouseleave').on('mouseenter mouseleave',async function(e) {
						if(e.type == 'mouseenter'){
							const dplayer_thumb = GM_getValue('dplayer-thumb');
							if(/lixian/.test(localHref) && !dplayer_thumb){
								console.log('跳过处理。');
								return;
							};
							leave=false;
							async function task() {
								if (leave) {
									if(ThumbUrl) {
										$('#thumbPreview').hide();
										URL.revokeObjectURL(ThumbUrl);
										ThumbUrl = false;
									};
									clearTimeout(hoverTimer);
									clearInterval(hoverTimer);
									return;
								};

								const currentTimeText = data.timeEl.text();
								const currentTime = formatDate(currentTimeText,true);;
								const timeChange = Math.abs(lastTime - currentTime);
								console.log(`当前时间: ${currentTimeText}, 转换为秒数: ${currentTime}s, 时间变化: ${timeChange}s`);

								if (!firstTrigger && timeChange < 60){
									console.log('条件未满足，跳过处理。');
									return;
								};
								
								if(ThumbUrl) {
									$('#thumbPreview').hide();
									URL.revokeObjectURL(ThumbUrl);
									ThumbUrl = false;
								};
								
								lastTime = currentTime;
								ThumbUrl = await clipper.getPreview(data.pid,currentTime,data.type);
							};
							
							hoverTimer = setTimeout(() => {
								task(); 
								firstTrigger = false;
								hoverTimer = setInterval(task, 3000); 
							}, 1500); 
						}else if(e.type == 'mouseleave'){
							leave=true;
							firstTrigger = true;
							if(ThumbUrl) {
								$('#thumbPreview').hide();
								URL.revokeObjectURL(ThumbUrl);
								ThumbUrl = false;
							};
							clearTimeout(hoverTimer);
							clearInterval(hoverTimer);
							console.log('鼠标已离开，清除计时器。');
						};
					});
			
				} catch (error) {
					console.error('缩略图生成失败:', error);
				};
			},
			
		};
	}();

	const Xplayer = function(){

		const storage = localforage.createInstance({
			name: '115optimize',
			storeName: 'player',
			version: 1,
			description: "dplayer缓存数据",
			driver: localforage.INDEXEDDB
		});
		 
		const sub = localforage.createInstance({
			name: '115optimize',
			storeName: 'subtitle',
			version: 1,
			description: "字幕缓存数据",
			driver: localforage.INDEXEDDB
		});
		
		var dp = null;
		var hlsBox = null;
		
		return {

			cleanCache:async function (){

				const lastCleanTime = await storage.getItem('lastCleanTime') || 0;
				const now = new Date().getTime();
				if(now - lastCleanTime > 7 * 24 * 60 * 60 * 1000) {

					await storage.setItem('m3u8List', {});

					const keys = await sub.keys();
					await Promise.all(
						keys.filter(k => k.startsWith('video_'))
							.map(k => sub.removeItem(k))
					);

					await storage.setItem('lastCleanTime', now);
					console.log('Xplayer: 已清理过期的m3u8清单缓存和字幕缓存');
				};
				
			 },

			initPlayerConfig:async function () {
				const configs = {
					"dplayer-speed": 1,
					"dplayer-position": true,
					"autoNextOK": false,
					"uploadStop": true,
					"skipPosition": false,
					"hasMemoryDisplay": false,
					"jumpStart": "",
					"jumpEnd": "",
					"qualityIndex": 0,
					"qualityChange": false,
					"currentAudioIndex": 0,
					"currentVideoIndex": '',
					"m3u8Info": {},
					"m3u8List": [],
					"videoList": [] 
				};
				
				try {
					const initTasks = Object.entries(config).map(async ([key, value]) => {
						if (await storage.getItem(key) === null) {
							await storage.setItem(key, value);
						}
					});
					await Promise.all(initTasks);
					return true;
				} catch (error) {
					console.error('初始化播放器配置失败:', error);
					return false;
				}
			},

			addDPlayer:async function () {
				$('pre').remove();
				$('body').html('');

				var currentVideoPid = GM_getValue('currentVideoPid');
				var m3u8Info = await Xplayer.setM3u8(currentVideoPid);
				if(!m3u8Info){
					alert('获取视频播放数据失败！请刷新页面重试');
					return;
				};
				$('head').html(`<meta http-equiv="Content-Type" content="text/html; charset=GBK"><title>${m3u8Info.name}</title>`);
				$('body').css({'margin':'0','padding':'0','border':'0','outline':'0','background':'transparent'});
				$('body').append(`<div id="dplayer"></div><div id="thumbPreview" style="position:absolute;z-index:999;display:none;"></div>`);
				
				Xplayer.cleanCache();
				Xplayer.initPlayerConfig();
				GM_addStyle(GM_getResourceText('dplayerCss'));
				Xplayer.dPlayerStart(m3u8Info);
				
			},

			setM3u8:function(pid,type){
				return new Promise(async function(resolve,reject){

					const m3u8List = await storage.getItem('m3u8List') || {};
					if(m3u8List[pid]) {

						console.log(' Xplayer:从缓存获取到m3u8信息。');
						await storage.setItem('m3u8Info', m3u8List[pid]);
						savePath(m3u8List[pid].definition_title);
						resolve(m3u8List[pid]);
						return;
					};

					var herfLink = 'https://webapi.115.com/files/video?pickcode='+pid;
					AjaxCall(herfLink,async function(error,htmlTxt){
						var json = JSON.parse(htmlTxt);
						if (!json.state) {
							console.log('获取m3u8地址失败：' +json.error+'\n'+herfLink);
							resolve(false);
							return ;
						} else if (json.queue_url) {
							var video={ pid:pid, sha:json.sha1 };
							transcoding(video);
							resolve(false);
							return;
						};
						console.log(' Xplayer:从网络获取到m3u8信息。');

						var multitrack_title = json.multitrack_list ? json.multitrack_list.map(track => {
							var title = track.title
								.replace(/.+声：|.+-(?!\w)|\[\S+$|(DD|AA|DT)\S+$|：/gi, '')
								.trim();

							if(/eng(lish)?|英/i.test(title)) return '英语';
							if(/cantonese|yue(-hk)?|粤|粵|广/i.test(title)) return '粤语';
							if(/mandarin|chi|chs|zh-cn|simplified|普通|中文|國語|国语/i.test(title)) return '国语';
							if(/jap(anese)?|jp|ja|日/i.test(title)) return '日语';
							if(/kor(ean)?|ko|韩/i.test(title)) return '韩语';
							if(/fre(nch)?|fr|法/i.test(title)) return '法语';
							if(/ger(man)?|de|德/i.test(title)) return '德语';
							if(/rus(sian)?|ru|俄/i.test(title)) return '俄语';
							if(/span(ish)?|es|西/i.test(title)) return '西班牙语';
							return title;
						}) : [];

						var definition_title = json.definition_list ? Object.values(json.definition_list) : [];
						
						var m3u8Info = {
							name: json.file_name,
							pid: pid,
							cid: json.parent_id,
							fid: json.file_id,
							size: change(json.file_size),
							sha: json.sha1,
							url: json.video_url.replace(/http(s)?/,'https'),
							multitrack_title: multitrack_title,
							definition_title: definition_title
						};

						m3u8List[pid] = m3u8Info;
						await storage.setItem('m3u8List',m3u8List);
						await storage.setItem('m3u8Info',m3u8Info);
						savePath(definition_title);
						resolve(m3u8Info);
					});
					async function savePath(definition_title){
						GM_setValue('currentVideoPid',pid);

						var qualityIndex;
						if (definition_title.length ==1 || G.get('play_Quality') =='最高' || G.get('play_Quality') =='原码'){
							qualityIndex = definition_title.length - 1;
						}else if(definition_title.length >1 && G.get('play_Quality') =='次高'){
							qualityIndex = definition_title.length - 2;
						}else{
							qualityIndex = 0;
						};
						await storage.setItem('qualityIndex',qualityIndex);
					};
				});
			},

            dPlayerStart:async function(m3u8Info) {
				try {
					var quality =[{'name':'1080P','url':m3u8Info.url,'type':'hls'}];

					GM_setValue('played',0);
					await storage.setItem('autoNextOK',false);
					await storage.setItem('uploadStop',true);
					await storage.setItem('hasMemoryDisplay',false);
					await storage.setItem('currentAudioIndex',0);
                } catch (error) {
					console.error('获取播放数据失败:', error);
					return;
				};

				console.log('初始化DPlayer配置');
				var options = {
                    container: $('#dplayer')[0], 
                    video: {
                        quality: quality, 
                        defaultQuality: 0, 
						customType: {
							hls:function(video, dp) {
								if (dp.plugins.hls) {
									delete dp.plugins.hls;
								};
								var hls = hlsBox =  new Hls({
									manifestLoadingTimeOut: 10000,  
									manifestLoadingMaxRetry: 3     
								});
								dp.plugins.hls = hls;

								let src = video.src;
								if(/^\//.test(src)) {
									src = 'https:'+src;
								};

								hls.on(Hls.Events.LEVEL_SWITCHED,async function(event, data) {
									if(await storage.getItem('qualityChange')){
										dp.notice('清晰度切换完成',3000);
									};
								});

								hls.on(Hls.Events.MANIFEST_PARSED,async function(event, data) {
									const levels = hls.levels;
									if (levels && levels.length > 0) {
										var qualityIndex = await storage.getItem('qualityIndex');
										if (qualityIndex != hls.currentLevel && hls.currentLevel !=-1){
											hls.currentLevel = qualityIndex;
											console.log('切换默认清晰度完成');
										};
									};
									
									if (data.audioTracks && data.audioTracks.length > 1) {

										console.log('音轨数量:'+data.audioTracks.length);
									}
								});
								
								hls.on(Hls.Events.ERROR,async function(event, data) {
									if(data.fatal) {
										switch(data.type) {
											case Hls.ErrorTypes.NETWORK_ERROR:
												console.error('网络错误:',data.details);
												hls.startLoad();
												break;
											case Hls.ErrorTypes.MEDIA_ERROR:
												if(data.details === 'bufferAppendError') {
													console.error('音频缓冲区错误，尝试重新加载...');
													if(typeof this.retryCount === 'undefined') {
														this.retryCount = 0;
													}
													this.retryCount++;
													if(this.retryCount < 3) { 
														setTimeout(() => {
															hls.recoverMediaError();
														}, 1500); 
													} else {

														var audioTracks = hls.audioTracks;
														var currentTrack = hls.audioTrack;
														if(audioTracks.length > 1) {
															currentTrack++;
															currentTrack %= audioTracks.length;
															hls.audioTrack = currentTrack;
															this.retryCount = 0; 
															hls.recoverMediaError();
															await storage.setItem("currentAudioIndex", currentTrack);
															Xplayer.setAudioTrack(true);
															console.log('切换音轨至:', currentTrack);
														} else {
															dp.notice('视频加载失败，达到最大重试次数且无法切换音轨',5000);
														}
													}
												}
												break;
											default:
												console.error('未知HLS错误:', data.error);
										}
									}
								});						
								
								try {
									hls.loadSource(src);
									hls.attachMedia(video);
								} catch(e) {
									console.error('HLS初始化失败:', e);
								}
							}
							
						}
                    },

                    subtitle: {
                        url: "",
                        type: "webvtt",
                        fontSize: (await sub.getItem("dplayer-subtitle-fontSize") || 4) + "vh",
                        bottom: (await sub.getItem("dplayer-subtitle-bottom") || 1) + "%",
                        color: await sub.getItem("dplayer-subtitle-color") || "#ffffff",
                    },

                    autoplay: true,
                    hotkey: false,
                    volume: 1.0,
                    playbackSpeed: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 3, 4, 5],
                    contextmenu:
					[
						{
							text: '下载视频',
							click:async function(t) {
								const newVideo = await storage.getItem('m3u8Info');
								var key={pc:newVideo.pid,fid:newVideo.fid};
								download(key).then(function(data){
									if(data[0]){
										GM_openInTab(data[0]);
										if(G.get('show_sha')){
											setTimeout(function(){
												prompt('文件下载中，校验码(SHA1)为：',newVideo.sha);
											}, 1000);
										};
									}else{
										alert('下载失败！'+data[1]);
									};
								});
							}
						},
						{
							text: '删除视频',
							click:async function(t) {
								const newVideo = await storage.getItem('m3u8Info');
								dp.pause();
								var a = confirm('确认删除 '+newVideo.name+' 视频文件？');
								if (a){
									offline.del(newVideo.fid)
								};
							}
						},
						{
							text: '查看文件夹',
							click:async function(t) {
								const newVideo = await storage.getItem('m3u8Info');
								GM_openInTab(`https://115.com/?cid=${newVideo.cid}&offset=0&mode=wangpan`,false);
							}
						},
						{
							text: '删除文件夹',
							click:async function(t) {
								const newVideo = await storage.getItem('m3u8Info');
								var a = confirm('确认删除 '+newVideo.name+' 视频所属文件夹？');
								if (a){
									offline.del(newVideo.cid);
								};
							}
						},
						{
							text: '设置星标',
							click:async function(t) {
								const newVideo = await storage.getItem('m3u8Info');
								var n=1;
								offline.setStar(newVideo.fid,n);
							}
						},
						{
							text: '取消星标',
							click:async function(t) {
								const newVideo = await storage.getItem('m3u8Info');
								var n=0;
								offline.setStar(newVideo.fid,n);
							}
						},
						{
							text: '重命名',
							click:async function(t) {
								const newVideo = await storage.getItem('m3u8Info');
								offline.newName(newVideo.fid,newVideo.name);
							}
						}
					]
				
                };

                try {

                    var dplayer = dp = new DPlayer(options);

                    Xplayer.playerReady(dplayer,async function(dp) {
                        dp.options.hotkey || Xplayer.dPlayerHotkey();
						Xplayer.initAspectRatio();
						Xplayer.initSetupRotate();
						Xplayer.addHotkeyGuide();
                        Xplayer.autoSkipPlayNext();
                        Xplayer.memoryPlay();
                        Xplayer.playSetting();
                        Xplayer.getPlaylist();
						Xplayer.setQualityItem();
						Xplayer.setAudioTrack();
						Xplayer.autobox();
                        Xplayer.getSubList(true,function (subList) {
                            if (subList) {
								Xplayer.selectSubtitles(subList);
								setTimeout(() => {
									Xplayer.subtitleStyle();
									Xplayer.subtitleOffset(subList,false);
								},200);
							};
                        });

						dp.speed(await storage.getItem("dplayer-speed") || 1);
						dp.on('ratechange',async function() {
                            dp.notice("播放速度：" + dp.video.playbackRate+'倍');
                            await storage.getItem("dplayer-speed") == dp.video.playbackRate || await storage.setItem("dplayer-speed", dp.video.playbackRate);
                        });
						
						dp.on('play',async function(){
							var stop = await storage.getItem('uploadStop');
							if(stop){
								await storage.setItem('uploadStop',false);
								Xplayer.updateTime();
							};
						});

						dp.on('seeked', function(){
							if(dp.video.paused){
								Xplayer.updateTime();
							};
						});

						dp.on('loadeddata',async function() {
							dp.notice('视频加载完成，开始播放...',3000);
							if(!document.hidden){
								dp.play();
							};
							
							const pid1 = GM_getValue('currentVideoPid');
							const video = await storage.getItem('m3u8Info');
							const m3u8 = await getM3u8(video,3);
							const m3u8Url= m3u8[0][0].url;
							const data={
								pid:pid1,
								url:m3u8Url,
								barEl:$('.dplayer-bar-wrap'),
								timeEl:$('.dplayer-bar-time'),
								type:'dplayer'
							};
							clipper.triggerPreview(data);

							if($('.openPiP').length==0){
								$('.dplayer-quality').eq('-1').after(`
									<div class="dplayer-icon openPiP" title="画中画" data-balloon-pos="up">
										<span class="dplayer-icon-content"><svg width="22" height="22" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"><g fill="#E6E6E6" fill-rule="evenodd"><path d="M17 4a2 2 0 012 2v6h-2V6.8a.8.8 0 00-.8-.8H4.8a.8.8 0 00-.794.7L4 6.8v8.4a.8.8 0 00.7.794l.1.006H11v2H4a2 2 0 01-2-2V6a2 2 0 012-2h13z"></path><rect x="13" y="14" width="8" height="6" rx="1"></rect></g></svg></span>
									</div>`);
							};
						});

						unsafeWindow.dp = dp;
						unsafeWindow.$ = $;
						unsafeWindow.hls = hlsBox;
						
						$('#dplayer').click();
						$('.dplayer-menu').css('width','98px');
						$('.dplayer-setting-loop,.dplayer-mobile-play,.dplayer-menu-item:gt(-3),.dplayer-full-in-icon').hide();
						
                    });
                } catch (error) {
                    console.error("dplayer播放器创建失败", error);
                }
				
            },

			dPlayerEvents:function (player) {
				Xplayer.playerReady(player, function(player) {
					Xplayer.autobox();
					Xplayer.memoryPlay();
					Xplayer.setQualityItem();
					Xplayer.setAudioTrack();
					Xplayer.autoSkipPlayNext();
					Xplayer.getSubList(true,function (subList) {
						if (subList) {
							Xplayer.selectSubtitles(subList);
							setTimeout(() => {
								Xplayer.subtitleStyle();
								Xplayer.subtitleOffset(subList,false);
							},200);
						};
                    });
				});
			},

			playerReady:function (player, callback) {

				if (player.isReady) {
					callback && callback(player);
				}else if (player.video.duration > 0 || player.video.readyState > 2) {
					player.isReady = true;
					callback && callback(player);
				}else {
					player.video.ondurationchange = function () {
						player.video.ondurationchange = null;
						player.isReady = true;
						callback && callback(player);
					}
				}
			},

			addHotkeyGuide:async function () {
				$('body').append(`<div class="dialog-box" style="z-index: 999;display: none; outline: none; top: 90.6667px; left: 377.5px;"><div class="dialog-handle"><a href="javascript:;" class="close">关闭</a></div><div class="dialog-header" ws_property="1"><h3 style="cursor: default; user-select: none;">快捷键指南</h3></div><div class="shortkey-guide-dialog"><div class="cell"><div class="key-line"><div class="name">暂停/播放</div><div class="key"><em>空格</em><span>/</span><em>K</em></div></div><div class="key-line"><div class="name">后退10秒</div><div class="key"><em>J</em></div></div><div class="key-line"><div class="name">前进10秒</div><div class="key"><em>L</em></div></div><div class="key-line"><div class="name">后退5秒</div><div class="key"><em>→</em></div></div><div class="key-line"><div class="name">前进5秒</div><div class="key"><em>←</em></div></div><div class="key-line"><div class="name">跳转到视频开头</div><div class="key"><em>0</em></div></div><div class="key-line"><div class="name">跳转到视频的10%到90%处</div><div class="key"><em>1-9</em></div></div><div class="key-line"><div class="name">切换全屏</div><div class="key"><em>Enter</em></div></div><div class="key-line"><div class="name">进入全屏</div><div class="key"><em>F</em></div></div><div class="key-line"><div class="name">退出全屏</div><div class="key"><em>Esc</em></div></div></div><s class="split"></s><div class="cell"><div class="key-line"><div class="name">降低播放速度</div><div class="key"><em>&lt;</em></div></div><div class="key-line"><div class="name">提高播放速度</div><div class="key"><em>&gt;</em></div></div><div class="key-line"><div class="name">静音/取消静音</div><div class="key"><em>M</em></div></div><div class="key-line"><div class="name">音量增加</div><div class="key"><em>滚轮向上</em><span>/</span><em>↑</em></div></div><div class="key-line"><div class="name">音量减少</div><div class="key"><em>滚轮向下</em><span>/</span><em>↓</em></div></div><div class="key-line"><div class="name">开启/关闭字幕</div><div class="key"><em>C</em></div></div><div class="key-line"><div class="name">跳转到播放列表第一个视频</div><div class="key"><em>Home</em></div></div><div class="key-line"><div class="name">跳转到播放列表最后一个视频</div><div class="key"><em>End</em></div></div><div class="key-line"><div class="name">播放下一个视频</div><div class="key"><em>N</em></div></div><div class="key-line"><div class="name">播放上一个视频</div><div class="key"><em>P</em></div></div></div></div></div>`);
				GM_addStyle(`.shortkey-guide-dialog{display:-webkit-box;display:-ms-flexbox;display:flex;padding:30px;}.shortkey-guide-dialog .split{-ms-flex-negative:0;flex-shrink:0;width:1px;margin:0 40px;background:#edeeef}.shortkey-guide-dialog .cell{-webkit-box-flex:1;-ms-flex:1;flex:1}.shortkey-guide-dialog .key-line{display:-webkit-box;display:-ms-flexbox;display:flex}.shortkey-guide-dialog .key-line:not(:last-child){margin-bottom:20px}.shortkey-guide-dialog .key-line .name{-webkit-box-flex:1;-ms-flex:1;flex:1;min-width:0}.shortkey-guide-dialog .key-line .key{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.shortkey-guide-dialog .key-line .key em{-webkit-box-sizing:border-box;box-sizing:border-box;min-width:24px;padding:0 4px;text-align:center;font-size:12px;line-height:24px;background:#fafafa;border:1px solid rgba(26,39,52,.1);border-radius:4px;-webkit-box-shadow:0 2px 0 0 rgba(0,0,0,.04);box-shadow:0 2px 0 0 rgba(0,0,0,.04)}.shortkey-guide-dialog .key-line .key span{-ms-flex-negative:0;flex-shrink:0;margin:0 4px;font-size:12px}.dialog-box{position:absolute;z-index:10010;width:700px;border-radius:8px;background:#fff;box-shadow:0 2px 20px 0 rgba(170,175,187,.3)}.dialog-handle{position:absolute;top:19px;right:23px;z-index:12}.dialog-handle .close{float:left;width:30px;height:30px;font-size:0;color:transparent}.dialog-handle .close:after{content:"";position:absolute;top:50%;left:50%;width:16px;height:16px;margin:-8px 0 0 -8px;background-image:url(https://cdnres.115.com/site/static/style_v10.0/common/images/icon_dialog_close.svg?_vh=73e654c_88);background-repeat:no-repeat}.dialog-handle .close.hover:after,.dialog-handle .close:hover:after{background-position:0 -16px}html:root .dialog-header{position:relative;display:flex;align-items:center;z-index:11;min-height:32px;padding:18px 30px;line-height:2px}`);
				$('.dplayer-quality-icon').eq(-1).parent('.dplayer-quality').after(`
						<div class="dplayer-icon shortkey" title="快捷键指南" data-balloon-pos="up">
							<span class="dplayer-icon-content">
							<svg viewBox="0 0 24 24" id="meteor-icon-kit__regular-question-circle" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path fill-rule="evenodd" clip-rule="evenodd" d="M24 12C24 18.6274 18.6274 24 12 24C5.37258 24 0 18.6274 0 12C0 5.37258 5.37258 0 12 0C18.6274 0 24 5.37258 24 12ZM12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM12 15C12.5523 15 13 15.4477 13 16C13 16.5523 12.5523 17 12 17C11.4477 17 11 16.5523 11 16C11 15.4477 11.4477 15 12 15ZM11 10C11 10.5523 10.5523 11 10 11C9.44771 11 9 10.5523 9 10C9 8.11438 10.1144 7 12 7C13.8856 7 15 8.11438 15 10C15 11.1817 14.5659 11.7713 13.6413 12.3496C13.585 12.3847 13.585 12.3847 13.5335 12.417C13.0871 12.6987 13 12.8324 13 13.5C13 14.0523 12.5523 14.5 12 14.5C11.4477 14.5 11 14.0523 11 13.5C11 12.0839 11.4788 11.3485 12.4665 10.7255C12.5262 10.688 12.5262 10.688 12.5807 10.6539C12.9784 10.4052 13 10.3758 13 10C13 9.21895 12.781 9 12 9C11.219 9 11 9.21895 11 10Z" fill="#758CA3"></path></g></svg>
							</span>
						</div>`);
				
				
				$('body').on('click','.dplayer-icon.shortkey,.dialog-handle .close',function(e){
					if($(this).is('.dplayer-icon.shortkey')){
						$('.dialog-box').toggle();
						if($('.dialog-box').is(':visible')) {
							$('.dplayer-mask').removeClass('dplayer-mask-show');
						}
					}else if($(this).is('.dialog-handle .close')){
						$('.dialog-box').hide(); 
					};
					return false; 
				});
				
			},

			dPlayerHotkey:function () {
				if (window.dPlayerHotkey) return;  
				window.dPlayerHotkey = true;  

				document.addEventListener("keydown", (function(e) {
					const t = dp;

					if (t && document.getElementById("dplayer")) {
						var a = document.activeElement.tagName.toUpperCase(),
							n = document.activeElement.getAttribute("contenteditable");

						if ("INPUT" !== a && "TEXTAREA" !== a && "" !== n && "true" !== n) {
							var o, r = e || window.event;
							switch (r.keyCode) {
								case 32:  
								case 75:  
									r.preventDefault();
									t.toggle();
									break;
								case 74:  
									r.preventDefault();
									t.seek(t.video.currentTime - 10);
									break;
								case 76:  
									r.preventDefault();
									t.seek(t.video.currentTime + 10);
									break;
								case 37:  
									r.preventDefault();
									t.seek(t.video.currentTime - 5);
									break;
								case 39:  
									r.preventDefault();
									t.seek(t.video.currentTime + 5);
									break;
								case 48:  
									r.preventDefault();
									t.seek(0);
									break;
								case 49:  
								case 50:  
								case 51:  
								case 52:  
								case 53:  
								case 54:  
								case 55:  
								case 56:  
								case 57:  
									r.preventDefault();
									t.seek(t.video.duration * (r.keyCode - 48) / 10);
									break;
								case 13:  
									r.preventDefault();
									t.fullScreen.toggle();
									break;
								case 70:  
									r.preventDefault();
									t.fullScreen.request('browser');
									break;
								case 27:  
									r.preventDefault();
									t.fullScreen.cancel('browser');
									break;
								case 188:  
                                case 190:  
                                    r.preventDefault();
                                    const speeds = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 3, 4, 5];
                                    const current = t.video.playbackRate;
                                    let index = speeds.indexOf(current);
                                    if (index === -1) {
                                        index = speeds.indexOf(1);
                                    };
                                    const next = e.keyCode === 188 ? Math.max(index - 1, 0) : Math.min(index + 1, speeds.length - 1);
                                    t.speed(speeds[next]);
                                    break;
								case 77:  
									r.preventDefault();
									o = $(".dplayer-volume-icon");
									o && o.click();
									break;
								case 38:  
									r.preventDefault();
									o = t.volume() + .1;
									t.volume(Math.min(o, 1));
									break;
								case 40:  
									r.preventDefault();
									o = t.volume() - .1;
									t.volume(Math.max(o, 0));
									break;
								case 67:  
									r.preventDefault();
									t.subtitle.toggle();
									break;
								case 36:  
									r.preventDefault();
									o = $(".playlist-content .video-item");
									o && o.eq(0).click();
									break;
								case 35:  
									r.preventDefault();
									o = $(".playlist-content .video-item");
									o && o.eq(-1).click();
									break;
								case 78:  
									r.preventDefault();
									o = $(".next-icon");
									o && o.click();
									break;
								case 80:  
									r.preventDefault();
									o = $(".prev-icon");
									o && o.click();
									break;
								
							}
						}
					}
				}));

				document.addEventListener("wheel", function (event) {
					event = event || window.event;
					if ($(event.target).closest(".playlist-content").length) return;  
					var o, t = dp;;

					if (event.deltaY < 0) {
						o = t.volume() + .01;
						t.volume(Math.min(o, 1));
					} 

					else if (event.deltaY > 0) {
						o = t.volume() - .01;
						t.volume(Math.max(o, 0));
					}
				});
			},

			initAspectRatio:async function () {
				var dplayer_rotate = await storage.getItem("dplayer-rotate") || false;;
				if ($(".dplayer-icons-right .btn-select-aspectRatio").length || !dplayer_rotate) return;
				var html = '<div class="dplayer-quality btn-select-aspectRatio"><button class="dplayer-icon dplayer-quality-icon">比例</button><div class="dplayer-quality-mask"><div class="dplayer-quality-list">';
				html += '<div class="dplayer-quality-item" data-value="4:3">4:3</div><div class="dplayer-quality-item" data-value="16:9">16:9</div><div class="dplayer-quality-item" data-value="none">原始比例</div><div class="dplayer-quality-item" data-value="cover">自动裁剪</div><div class="dplayer-quality-item" data-value="fill">拉伸填充</div><div class="dplayer-quality-item" data-value="default">系统默认</div>';
				html += '</div></div></div>';
				if($(".next-icon").length>0){
					$(".next-icon").after(html);
				}else{
					$(".dplayer-icons-right").prepend(html);
				};
				$(".btn-select-aspectRatio .dplayer-quality-item").on("click", function() {
					var ratio = $(this).attr("data-value");
					Xplayer.aspectRatio(ratio);
				});
			},

			aspectRatio:function (ratio) {
				const { template: { videoWrap, video } } = dp;
				const ratios = { "default": "系统默认", "4:3": "4:3", "16:9": "16:9", "fill": "拉伸填充", "cover": "自动裁剪", "none": "原始比例" };
				!ratio ? (ratio = videoWrap.dataset.aspectRatio || "default") : dp.notice(`画面比例: ${ratios[ratio]}`);
				if (ratio === "default") {
					setStyle(video, 'width', null);
					setStyle(video, 'height', null);
					setStyle(video, 'padding', null);
					setStyle(video, 'object-fit', null);
					delete videoWrap.dataset.aspectRatio;
				}else if (["4:3", "16:9"].includes(ratio)) {
					const ratioArray = ratio.split(':').map(Number);
					const { videoWidth, videoHeight } = video;
					const { clientWidth, clientHeight } = videoWrap;
					const videoRatio = videoWidth / videoHeight;
					const setupRatio = ratioArray[0] / ratioArray[1];
					if (videoRatio > setupRatio) {
						const percentage = (setupRatio * videoHeight) / videoWidth;
						setStyle(video, 'width', `${percentage * 100}%`);
						setStyle(video, 'height', '100%');
						setStyle(video, 'padding', `0 ${(clientWidth - clientWidth * percentage) / 2}px`);
					} else {
						const percentage = videoWidth / setupRatio / videoHeight;
						setStyle(video, 'width', '100%');
						setStyle(video, 'height', `${percentage * 100}%`);
						setStyle(video, 'padding', `${(clientHeight - clientHeight * percentage) / 2}px 0`);
					}
					setStyle(video, 'object-fit', 'fill');
					videoWrap.dataset.aspectRatio = ratio;
				}else if (["fill", "cover", "none"].includes(ratio)) {
					setStyle(video, 'width', null);
					setStyle(video, 'height', null);
					setStyle(video, 'padding', null);
					setStyle(video, 'object-fit', ratio);
					videoWrap.dataset.aspectRatio = ratio;
				}
				function setStyle (element, key, value) {
					Xplayer.autobox();
					element.style[key] = value;
					return element;
				}
			},

			initSetupRotate:async function () {
				var dplayer_rotate = await storage.getItem("dplayer-rotate") || false;
				if ($(".dplayer-icons-right .btn-select-setupRotate").length || !dplayer_rotate) return;
				setTimeout(() => {
					var html = '<div class="dplayer-quality btn-select-setupRotate"><button class="dplayer-icon dplayer-quality-icon">方向</button><div class="dplayer-quality-mask"><div class="dplayer-quality-list">';
					html += '<div class="dplayer-quality-item" data-value="left">向左旋转</div><div class="dplayer-quality-item" data-value="right">向右旋转</div><div class="dplayer-quality-item" data-value="horizontal">水平翻转</div><div class="dplayer-quality-item" data-value="vertical">垂直翻转</div><div class="dplayer-quality-item" data-value="default">系统默认</div>';
					html += '</div></div></div>';
					
					$(".btn-select-aspectRatio").after(html);
					$(".btn-select-setupRotate .dplayer-quality-item").on("click", function() {
						var rotate = $(this).attr("data-value");
						Xplayer.setupRotate(rotate);
					});
				},200);
			},

			setupRotate:function (rotate) {
				var d = dp.video.style;

				switch(rotate) {
					case 'left':// 向左旋转
						d['transform'] = 'rotate(-90deg)';
						break;
					case 'right':// 向右旋转
						d['transform'] = 'rotate(90deg)';
						break;
					case 'horizontal':// 水平翻转
						d['transform'] = 'scaleX(-1)';
						break; 
					case 'vertical':// 垂直翻转
						d['transform'] = 'scaleY(-1)';
						break;
					case 'default':// 系统默认
						d['transform'] = ''; 
						break;	
				}
				
				const rotates = { "left": "向左旋转", "right": "向右旋转", "horizontal": "水平翻转", "vertical": "垂直翻转", "default": "系统默认" };
				dp.notice(`画面方向: ${rotates[rotate]}`);

			},

			setAudioTrack:function (type) {
				setTimeout(async () => {
					const currentAudioIndex = await storage.getItem("currentAudioIndex") || 0;
					if(type){
						$('.audioTrack').css('opacity', 1);
						$('.audioTrack').eq(currentAudioIndex).css('opacity', 0.5);
						$('.audioTrack').eq(currentAudioIndex-1).remove();
						if($('.audioTrack').length==1)$('.btn-select-audioTrack').remove();
						return;
					};
					$('.btn-select-audioTrack').remove();
					var m3u8Info = await storage.getItem('m3u8Info');
					var multitrack_title = m3u8Info.multitrack_title;
					if (multitrack_title.length==0){
						return;
					};
					var html = '<div class="dplayer-quality btn-select-audioTrack"><button class="dplayer-icon dplayer-quality-icon">音轨</button><div class="dplayer-quality-mask"><div class="dplayer-quality-list">';
					for (let i = 0; i < multitrack_title.length; i++) {
						html += `<div class="dplayer-quality-item audioTrack" style="${i === currentAudioIndex ? 'opacity:0.5' : 'opacity:1'}" data-value="${i}">${m3u8Info.multitrack_title[i]}</div>`;
					}
					$('.defaultQuality').before(html);
					$('.btn-select-audioTrack .dplayer-quality-item').off("click").on("click",async function() {
						if ($(this).css("opacity") == 0.5) return;
						$(this).css("opacity", 0.5);
						$(this).siblings().css("opacity", 1);
						var index = $(this).attr("data-value");
						hlsBox.audioTrack = index;
						await storage.setItem("currentAudioIndex", index);
						var newName = $(this).text();
						dp.notice(`已切换音轨：${newName},10s后生效`,3000);
						console.log('当前音轨索引:'+index);
					});
				}, 300)

			},

			setQualityItem:function () {
				setTimeout(async () => {

					var qualityIndex = await storage.getItem('qualityIndex') || 0;
					var m3u8Info = await storage.getItem('m3u8Info');
					await storage.setItem('qualityChange',false);
					var definition_title = m3u8Info.definition_title;
					let el ='';
					for (var i=0; i<definition_title.length; i++){
						if ( i === qualityIndex) {
							dp.template.qualityButton.textContent = definition_title[i];
							el += (`<div class="dplayer-quality-item dplayer-qualitySwitch" style="opacity:0.5" data-index=${i}>${definition_title[i]}</div>`);
							continue;
						}; 
						el += (`<div class="dplayer-quality-item dplayer-qualitySwitch" style="opacity:1" data-index=${i}>${definition_title[i]}</div>`);
					};
					$('.dplayer-quality-item[data-index]').closest('.dplayer-quality').addClass('defaultQuality');
					$('.defaultQuality .dplayer-quality-list').html(el);
					$('.dplayer-qualitySwitch').off("click").on("click",async function() {
						if ($(this).css("opacity") == 0.5) return;
						$(this).css("opacity", 0.5);
						$(this).siblings().css("opacity", 1);

						var newName = $(this).text();
						var index = Number($(this).attr("data-index"));
						dp.template.qualityButton.textContent=newName;
						dp.notice(`正在切换 ${newName} 清晰度`,3000);
						hlsBox.currentLevel = index;
						await storage.setItem('qualityChange',true);
						await storage.setItem('qualityIndex',index);
						return false;
					});

					if(definition_title.length >1){
						$('.defaultQuality button').css('color','#00FF00');
					}else{
						$('.defaultQuality button').css('color','#FFFFFF');
					};
					
				}, 200)
			},

			autobox:function (){
				if (!$('video').length) {
					console.log('未找到video元素，跳过尺寸调整');
					return;
				}
				if (document.compatMode == 'BackCompat') {
					var cW = document.body.clientWidth;
					var cH = document.body.clientHeight;
					var sW = document.body.scrollWidth;
					var sH = document.body.scrollHeight;

				}else{
					var cW = document.documentElement.clientWidth;
					var cH = document.documentElement.clientHeight;
					var sW = document.documentElement.scrollWidth;
					var sH = document.documentElement.scrollHeight;

				};
				var iW=window.innerWidth;
				var iH=window.innerHeight;
				var eW = $('video')[0].offsetWidth;
				var eH = $('video')[0].offsetHeight;
				
				if(sW > (iW || cW)){
					cW=iW || cW;
				};
				if(sH > (iH ||cH)){
					cH=iH || cH;
				};
				$('video').css({'width':cW+'px','height':cH+'px'});
			},

			videoSwitch:async function(m3u8Info) {

				document.title = m3u8Info.name;
				GM_setValue('played',0);
				await storage.setItem('autoNextOK',false);
				await storage.setItem('uploadStop',true);
				await storage.setItem('hasMemoryDisplay',false);
				await storage.setItem('currentAudioIndex',0);
				$('.dplayer-subtitle b').remove();
				hlsBox.stopLoad($('video')[0]); 
				hlsBox.detachMedia($('video')[0]);
				dp.switchVideo({
					url: m3u8Info.url,
					type: 'hls',
				});
				Xplayer.dPlayerEvents(dp);
			},

			updateTime:function() {
				let update;
				let lastTime = 0;
				clearInterval(update);
				update = setInterval(async function (){
					var pid = GM_getValue('currentVideoPid');
					var end=GM_getValue('played') || 0;
					var newTime=dp.video.currentTime.toFixed(0);
					var interval =parseInt(dp.video.currentTime-lastTime);
					lastTime=dp.video.currentTime;
					const uploadStop = await storage.getItem('uploadStop');
					if(uploadStop) {
						clearInterval(update);
						return
					};
					
					var key = {
						'op': 'update',
						'pick_code': pid,
						'time': end? 0:newTime,
						'definition': end,
						'category': 1
					};
					
					var history_url='https://webapi.115.com/files/history';
					if(end || (lastTime >= 30 && Math.abs(interval) > 4)){
						offline.getData(history_url,$.param(key)).then(function(json){
							if(typeof json == 'undefined'){
								console.log('服务器错误！访问过于频繁，暂停上传播放记录15分钟。');
								storage.setItem('uploadStop',true);
								clearInterval(update);
								setTimeout(function(){
									storage.setItem('uploadStop',false);
								},1000*900);
								return
							};
							json.state? console.log('上传播放记录成功！'):console.log('上传播放记录失败，'+json.error);
						});
					};
					if(dp.video.paused || dp.video.error || end){ 
						storage.setItem('uploadStop',true);
						clearInterval(update);
						return;
					};
				}, 5000);
			},

			playSetting:async function () {

				if ($(".dplayer-setting-skipPosition").length) return;

				var html = `
					<div class="dplayer-setting-item dplayer-setting-jumpStart" style="display:none">
						<span class="dplayer-label" title="双击设置当前时间为跳过片头秒数">片头(秒)</span>
						<input type="text" name="dplayer-toggle" class="dplayer-toggle" style="height: 15px; font-size: 13px;border: 1px solid #fff;border-radius: 15px;">
					</div>
					<div class="dplayer-setting-item dplayer-setting-jumpEnd" style="display:none">
						<span class="dplayer-label" title="双击设置剩余时间为跳过片尾秒数">片尾(秒)</span>
						<input type="text" name="dplayer-toggle" class="dplayer-toggle" style="height: 15px; font-size: 13px;border: 1px solid #fff;border-radius: 15px;">
					</div>
					
					<div class="dplayer-setting-item dplayer-setting-skipPosition">
						<span class="dplayer-label">跳过片头片尾</span>
						<div class="dplayer-toggle">
							<input class="dplayer-toggle-setting-input-skipPosition" type="checkbox" name="dplayer-toggle">
							<label for="dplayer-toggle"></label>
						</div>
					</div>`;

				html += `<div class="dplayer-setting-item dplayer-setting-autoposition">
					<span class="dplayer-label">自动记忆播放</span>
					<div class="dplayer-toggle">
						<input class="dplayer-toggle-setting-input-autoposition" type="checkbox" name="dplayer-toggle">
						<label for="dplayer-toggle"></label>
					</div>
				</div>`;

				html += `<div class="dplayer-setting-item dplayer-setting-rotate">
					<span class="dplayer-label">调节比例/方向</span>
					<div class="dplayer-toggle">
						<input class="dplayer-toggle-setting-input-rotate" type="checkbox" name="dplayer-toggle">
						<label for="dplayer-toggle"></label>
					</div>
				</div>`;

				html += `<div class="dplayer-setting-item dplayer-setting-thumb">
					<span class="dplayer-label">缩略图预览</span>
					<div class="dplayer-toggle">
						<input class="dplayer-toggle-setting-input-thumb" type="checkbox" name="dplayer-toggle">
						<label for="dplayer-toggle"></label>
					</div>
				</div>`;

				$(".dplayer-setting-origin-panel").prepend(html);
				html = `<div class="dplayer-setting-item dplayer-setting-subtitle">
					<span class="dplayer-label">字幕设置</span>
				</div>`;
				$(".dplayer-setting-origin-panel").append(html);

				$(".dplayer-setting-subtitle").on("click", function() {
					Xplayer.subtitleSetting();
				});
				$(".dplayer-mask").on("click",function() {
					if ($(".subtitle-setting-box").is(":visible")) {
						$(".subtitle-setting-box").toggle();
						$(this).removeClass("dplayer-mask-show");
					}
				});

				var jumpStart = await storage.getItem("jumpStart") || 60; 
				var jumpEnd = await storage.getItem("jumpEnd") || 120; 
				var skipPosition = await storage.getItem("skipPosition"); 

				if (skipPosition) {
					$(".dplayer-toggle-setting-input-skipPosition").get(0).checked = true;
					$(".dplayer-setting-jumpStart").show();
					$(".dplayer-setting-jumpEnd").show();
				}

				var txt = $(".dplayer-setting-jumpStart .dplayer-toggle");
				txt.val(jumpStart);
				txt.change(function() {
					storage.setItem("jumpStart", txt.val());
					jumpStart = txt.val();
				});
				txt.on('input propertychange', function(e) {
					var text = txt.val().replace(/[^\d]/g, "");
					txt.val(text);
				});
				
				var txt1 = $(".dplayer-setting-jumpEnd .dplayer-toggle");
				txt1.val(jumpEnd);
				txt1.change(function() {
					storage.setItem("jumpEnd", txt1.val());
					jumpEnd = txt1.val();
				});
				txt1.on('input propertychange', function(e) {
					var text = txt.val().replace(/[^\d]/g, "");
					txt.val(text);
				});

				$(".dplayer-setting-skipPosition").on("click",async function() {
					var check = $(".dplayer-toggle-setting-input-skipPosition");
					var skipPosition = !check.is(":checked");
					$(".dplayer-toggle-setting-input-skipPosition").get(0).checked = skipPosition;
					await storage.setItem("skipPosition", skipPosition);
					
					if (skipPosition) {
						$(".dplayer-setting-jumpStart").show()
						$(".dplayer-setting-jumpEnd").show()
						txt.val(jumpStart);
						txt1.val(jumpEnd);
						storage.setItem("jumpStart", jumpStart);
						storage.setItem("jumpEnd", jumpEnd);
						if($(".dplayer-setting-loop .dplayer-toggle-setting-input").is(":checked")) {
							$(".dplayer-setting-loop .dplayer-toggle-setting-input").click();
						}
					}
					else{
						$(".dplayer-setting-jumpStart").hide()
						$(".dplayer-setting-jumpEnd").hide()
					}
				});

				$(".dplayer-setting-jumpStart, .dplayer-setting-jumpEnd").on("dblclick", function() {
					let currtime = 0, 
						duration = parseInt(dp.video.duration), currentTime = parseInt(dp.video.currentTime);
					if($(this).hasClass("dplayer-setting-jumpStart")){
						currtime = currentTime;
						storage.setItem("jumpStart", currtime);
					}
					else{
						currtime = duration - currentTime;
						storage.setItem("jumpEnd", currtime);
					}
					$(this).children("input").val(currtime)
				});

				await storage.getItem("dplayer-position").then(value => {
					value && ($(".dplayer-toggle-setting-input-autoposition").get(0).checked = true);
				});		
				$(".dplayer-setting-autoposition").on("click", function() {
					var check = $(".dplayer-toggle-setting-input-autoposition");
					var autoPosition = !check.is(":checked");
					$(".dplayer-toggle-setting-input-autoposition").get(0).checked = autoPosition;
					storage.setItem("dplayer-position", autoPosition);
				});

				await storage.getItem("dplayer-rotate").then(value => {
					value && ($(".dplayer-toggle-setting-input-rotate").get(0).checked = true);
				});		
				$(".dplayer-setting-rotate").on("click",async function() {
					var check = $(".dplayer-toggle-setting-input-rotate");
					var rotate = !check.is(":checked");
					$(".dplayer-toggle-setting-input-rotate").get(0).checked = rotate;
					await storage.setItem("dplayer-rotate", rotate);
					if(rotate){
						Xplayer.initAspectRatio();
						Xplayer.initSetupRotate();
					}else{
						$(".btn-select-aspectRatio,.btn-select-setupRotate").remove();
					};
					
				});

				var thumbSetting = GM_getValue("dplayer-thumb", true);
				GM_setValue("dplayer-thumb", thumbSetting);
				$(".dplayer-toggle-setting-input-thumb").get(0).checked = thumbSetting;
				$(".dplayer-setting-thumb").on("click", function() {
					var check = $(".dplayer-toggle-setting-input-thumb");
					var thumb = !check.is(":checked");
					$(".dplayer-toggle-setting-input-thumb").get(0).checked = thumb;
					GM_setValue("dplayer-thumb", thumb);
					if(thumb){
						dp.notice('缩略图预览已开启,连续预览时视频间隔需大于60s,鼠标停留3s.',3000);
					};	
				});

				$(".dplayer-setting-loop").on("click", function() {
					if ($(".dplayer-setting-loop .dplayer-toggle-setting-input").is(":checked") && skipPosition) {
						$(".dplayer-setting-skipPosition").click();
					}
					$(".dplayer-setting-icon").click();
				});
			},

			memoryPlay:async function () {
				var complete = await storage.getItem('hasMemoryDisplay');
				if (complete) return;
				await storage.setItem('hasMemoryDisplay',true);

				var jumpStart = await storage.getItem("jumpStart") || 60; 
				var jumpEnd = await storage.getItem("jumpEnd") || 120; 
				var skipPosition = await storage.getItem("skipPosition"); 

				const pid = GM_getValue('currentVideoPid');
				var dplayer_position = await storage.getItem("dplayer-position");
				getHistory(pid).then(function(memoryTime){
					if (skipPosition || dplayer_position) {
						if (skipPosition && jumpStart > 0 && (jumpStart > memoryTime || !dplayer_position) && jumpStart > dp.video.currentTime){
							var skipTime=jumpStart;
							var txt=' 秒后跳过片头：'+tranTime(skipTime);
						} else if(dplayer_position && memoryTime > 0 && memoryTime > jumpStart && memoryTime > dp.video.currentTime) {
							var skipTime=memoryTime;
							var txt=' 秒后跳到上次播放：'+tranTime(skipTime);
						}else{
							return;
						};

						$(".memory-play-wrap").remove();
						let countdown = 10;
						var el=$(`<div class="memory-play-wrap" style="display: block;position: absolute;left: 22px;bottom: 100px;font-size: 15px;padding: 7px;border-radius: 3px;color: #fff;z-index:100;background: rgba(0,0,0,.5);">
							<span class="countdown-text">${countdown}${txt}</span>&nbsp;&nbsp;
							<a href="javascript:void(0);" class="play-jump" style="text-decoration: none;color: #06c;">立即跳转</a>&nbsp;
							<em class="close-btn" style="display: inline-block;width: 15px;height: 15px;vertical-align: middle;cursor: pointer;background: url(https://nd-static.bdstatic.com/m-static/disk-share/widget/pageModule/share-file-main/fileType/video/img/video-flash-closebtn_15f0e97.png) no-repeat;"></em>
							</div>`);
						$(dp.container).append(el);

						const countdownInterval = setInterval(() => {
							countdown--;
							$(".countdown-text").text(countdown+txt);
							if(countdown <= 0) {
								clearInterval(countdownInterval);
							}
						}, 1000);
							
						var memoryTimeout = setTimeout(function () {
							clearInterval(countdownInterval);
							$(".memory-play-wrap").remove();
							dp.seek(skipTime - 1)
						}, countdown*1000);
						$(".memory-play-wrap .close-btn").click(function () {
							$(".memory-play-wrap").remove();
							clearTimeout(memoryTimeout);
						});
						$(".memory-play-wrap .play-jump").click(function () {
							clearInterval(countdownInterval);
							dp.seek(skipTime - 1);
							$(".memory-play-wrap").remove();
							clearTimeout(memoryTimeout);
						});
					};
				});

				document.onvisibilitychange = function () {
					if (document.visibilityState === "hidden") {
						Xplayer.updateTime();
					}
				};

				window.onbeforeunload = function () {
					Xplayer.updateTime();
				};

				function formatVideoTime (seconds) {
					var secondTotal = Math.round(seconds)
					, hour = Math.floor(secondTotal / 3600)
					, minute = Math.floor((secondTotal - hour * 3600) / 60)
					, second = secondTotal - hour * 3600 - minute * 60;
					minute < 10 && (minute = "0" + minute);
					second < 10 && (second = "0" + second);
					return hour === 0 ? minute + ":" + second : hour + ":" + minute + ":" + second;
				}
			},

			autoSkipPlayNext:function () {
				let lastCheckTime = 0;
				dp.on("timeupdate",async function () {
					const now = Date.now();
					if(now - lastCheckTime < 2000) return; 
					lastCheckTime = now;
					var autoNextOK = await storage.getItem('autoNextOK');
					var jumpStart = await storage.getItem("jumpStart") || 60; 
					var jumpEnd = await storage.getItem("jumpEnd") || 120; 
					var skipPosition = await storage.getItem("skipPosition"); 
					const $episode = $(".dplayer-icons-right #btn-select-episode");
					if (autoNextOK || !skipPosition || $episode.length==0) return;
					
					if (!this.skipTriggered && jumpEnd > 0 && dp.video.duration - dp.video.currentTime <= parseInt(jumpEnd) + 10 * dp.video.playbackRate) {
						this.skipTriggered = true;
						dp.notice('已跳过片尾'+jumpEnd+'秒', 3000);
						dp.pause();
						GM_setValue('played',1);

						await storage.setItem('autoNextOK',true);
						const currentVideoIndex = await storage.getItem('currentVideoIndex');
						const nextIndex = currentVideoIndex + 1;
						if (nextIndex < $(".video-item").length) {

							$(".memory-play-wrap").remove();
							let countdown = 10;
							var el=$(`<div class="memory-play-wrap" style="display: block;position: absolute;left: 22px;bottom: 100px;font-size: 15px;padding: 7px;border-radius: 3px;color: #fff;z-index:100;background: rgba(0,0,0,.5);">
								<span class="countdown-text">${countdown} 秒后自动播放下一集</span>&nbsp;&nbsp;
								<a href="javascript:void(0);" class="play-jump" style="text-decoration: none;color: #06c;">立即播放</a>&nbsp;
								<em class="close-btn" style="display: inline-block;width: 15px;height: 15px;vertical-align: middle;cursor: pointer;background: url(https://nd-static.bdstatic.com/m-static/disk-share/widget/pageModule/share-file-main/fileType/video/img/video-flash-closebtn_15f0e97.png) no-repeat;"></em>
								</div>`);
							$(dp.container).append(el);

							const countdownInterval = setInterval(() => {
								countdown--;
								$(".countdown-text").text(`${countdown} 秒后自动播放下一集  `);
								if(countdown <= 0) {
									clearInterval(countdownInterval);
								}
							}, 1000);
							async function autoPlay(){
								clearInterval(countdownInterval);
								$(".memory-play-wrap").remove();
								$(".next-icon").click();
							};

							var memoryTimeout = setTimeout(function() {
								autoPlay();
							}, countdown*1000);

							$(".memory-play-wrap .close-btn").click(function() {
								$(".memory-play-wrap").remove();
								clearTimeout(memoryTimeout);
							});

							$(".memory-play-wrap .play-jump").click(function () {
								autoPlay();
								clearTimeout(memoryTimeout);
							});
						} else {
							dp.notice('没有下一集了', 2000);
						};
						return;
					};
					GM_setValue('played',0);
				}.bind({skipTriggered: false}));
			},

			getPlaylist:async function(){
				if ($(".dplayer-icons-right #btn-select-episode").length>1) return;
				const m3u8Info = await storage.getItem('m3u8Info');
				const fileList = await folderList(m3u8Info.cid,m3u8Info.name,4);
				if (!fileList[0]){
					console.log('视频文件夹检索失败，无法生成播放列表，\n错误提示：'+fileList[1]);
					return;
				};

				const videoList = fileList[0].data.filter(function (item, index) { 

					return item.s > 1024*1024*20 && item.play_long > 180
				});

				if (videoList.length <= 1) return;

				let currentVideoIndex = videoList.findIndex(function (item, index) {
					return item.pc == m3u8Info.pid;
				});
				await storage.setItem('currentVideoIndex',currentVideoIndex);

				let elevideo = '';
				videoList.forEach((item, index) => {
					if (currentVideoIndex === index) {
						elevideo += `<div class="video-item active" title="${item.n.replace(/"/g, '&quot;')}" pid=${item.pc} style="background-color: rgba(0,0,0,.3);color: #0df;cursor: pointer;font-size: 14px;line-height: 35px;overflow: hidden;padding: 0 10px;text-overflow: ellipsis;text-align: left;white-space: nowrap;">${item.n}</div>`;
					} else {
						elevideo += `<div class="video-item" title="${item.n.replace(/"/g, '&quot;')}" pid=${item.pc} style="color: #fff;cursor: pointer;font-size: 14px;line-height: 35px;overflow: hidden;padding: 0 10px;text-overflow: ellipsis;text-align: left;white-space: nowrap;">${item.n}</div>`;
					}
				});

				const svg = `<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 32 32"><path d="M22 16l-10.105-10.6-1.895 1.987 8.211 8.613-8.211 8.612 1.895 1.988 8.211-8.613z"></path></svg>`;
				let html = `<button class="dplayer-icon dplayer-play-icon prev-icon" style="transform: rotate(-180deg)" title="上一集">${svg}</button>`;
				html += `<button id="btn-select-episode" class="dplayer-icon dplayer-quality-icon" title="展开播放列表">选集</button> 
					<div class="playlist-content" style="max-width: 80%;max-height: 330px;width: auto;height: auto;box-sizing: border-box;overflow: hidden;position: absolute;left: 0;transition: all .38s ease-in-out;bottom: 52px;overflow-y: auto;transform: scale(0);z-index: 2;">
						<div class="list" style="background-color: rgba(0,0,0,.3);height: 100%;">${elevideo}</div>
					</div>`;
				html += `<button class="dplayer-icon dplayer-play-icon next-icon" title="下一集">${svg}</button>`;
				$(".dplayer-icons-right").prepend(html);

				$("#btn-select-episode").on("click",async function() {
					const eleEpisode = $(".playlist-content");
					if (eleEpisode.css("transform").match(/\d+/) > 0) {
						eleEpisode.css("transform", "scale(0)");
					} else {
						eleEpisode.css("transform", "scale(1)");
						$(".dplayer-mask").addClass("dplayer-mask-show");
						const videoIndex = await storage.getItem('currentVideoIndex');
						const singleheight = $(".dplayer-icons-right .video-item")[0].offsetHeight;
						const totalheight = $(".dplayer-icons-right .playlist-content").height();
						$(".dplayer-icons-right .playlist-content").scrollTop((videoIndex + 1) * singleheight - totalheight / 2);
					}
				});

				$(".dplayer-mask").on("click", function() {
					const eleEpisode = $(".playlist-content");
					if (eleEpisode.css("transform").match(/\d+/) > 0) {
						eleEpisode.css("transform", "scale(0)");
						$(this).removeClass("dplayer-mask-show");
					}
				});

				$(".playlist-content .video-item").off("click").on("click",async function() {
					const $this = $(this);
					if ($this.hasClass("active")) return;

					$(".dplayer-mask").removeClass("dplayer-mask-show");
					const oldele = $(".video-item.active");
					oldele.removeClass("active");
					oldele.css({"background-color": "", "color": "#fff"});
					$this.addClass("active");
					$this.css({"background-color": "rgba(0,0,0,.3)", "color": "#0df"});
					
					await storage.setItem('currentVideoIndex',$this.index());

					const pid = $this.attr('pid');
					const m3u8Info = await Xplayer.setM3u8(pid, 3);
					if (m3u8Info){
						Xplayer.videoSwitch(m3u8Info);
						dp.notice('已切换到选中视频', 3000);
					}else{
						console.log('m3u8母文件获取失败。');
						return;
					};
					$(".playlist-content").css("transform", "scale(0)");
				});

				$(".prev-icon").off("click").on("click",async function() {
					const videoIndex = await storage.getItem('currentVideoIndex');
					const prevIndex = videoIndex - 1;
					if (prevIndex >= 0) {

						const $prevItem = $(".video-item").eq(prevIndex);
						$(".video-item.active").removeClass("active").css({"background-color": "", "color": "#fff"});
						$prevItem.addClass("active").css({"background-color": "rgba(0,0,0,.3)", "color": "#0df"});
						
						await storage.setItem('currentVideoIndex',prevIndex);
						const pid = $prevItem.attr('pid');
						console.log('pid:',pid);
						const m3u8Info = await Xplayer.setM3u8(pid, 3);
						if (m3u8Info) {
							Xplayer.videoSwitch(m3u8Info);
							dp.notice('已切换到上一集', 3000);
						};
					} else {
						dp.notice('没有上一集了', 3000);
					};
				});

				$(".next-icon").off("click").on("click",async function() {
					const videoIndex = await storage.getItem('currentVideoIndex');
					const nextIndex = videoIndex + 1;
					if (nextIndex < videoList.length) {

						const $nextItem = $(".video-item").eq(nextIndex);
						$(".video-item.active").removeClass("active").css({"background-color": "", "color": "#fff"});
						$nextItem.addClass("active").css({"background-color": "rgba(0,0,0,.3)", "color": "#0df"});
						
						await storage.setItem('currentVideoIndex',nextIndex);
						const pid = $nextItem.attr('pid');
						const m3u8Info = await Xplayer.setM3u8(pid, 3);
						if (m3u8Info) {
							Xplayer.videoSwitch(m3u8Info);
							dp.notice('已切换到下一集', 3000);
						};
					} else {
						dp.notice('没有下一集了', 3000);
					};
				});
				console.log('生成播放列表OK');
			},

			subtitleSetting:async function () {
				var subSetBox = $(".subtitle-setting-box");
				if (subSetBox.length) {
					return subSetBox.toggle();
				}else {
					var html = '<div class="dplayer-icons dplayer-comment-box subtitle-setting-box" style="display: block; z-index: 111; position: absolute; bottom: 10px;left:auto; right: 400px !important;"><div class="dplayer-comment-setting-box dplayer-comment-setting-open" >';
					html += '<div class="dplayer-comment-setting-color"><div class="dplayer-comment-setting-title">字幕颜色<input type="text" class="color-value" style="height: 16px;width: 70px;font-size: 14px;border: 1px solid #ffffff;border-radius: 3px;margin-left: 70px;color: black;text-align: center;"></div><label><input type="radio" name="dplayer-danmaku-color-1" value="#ffffff" checked=""><span class="subtitle-color" style="background: #ffffff;opacity:0.3;"></span></label><label><input type="radio" name="dplayer-danmaku-color-1" value="#e54256"><span class="subtitle-color" style="background: #e54256;opacity:0.3;"></span></label><label><input type="radio" name="dplayer-danmaku-color-1" value="#ffe133"><span class="subtitle-color" style="background: #ffe133;opacity:0.3;"></span></label><label><input type="radio" name="dplayer-danmaku-color-1" value="#64DD17"><span class="subtitle-color" style="background: #64DD17;opacity:0.3;"></span></label><label><input type="radio" name="dplayer-danmaku-color-1" value="#39ccff"><span class="subtitle-color" style="background: #39ccff;opacity:0.3;"></span></label><label><input type="radio" name="dplayer-danmaku-color-1" value="#D500F9"><span class="subtitle-color" style="background: #D500F9;opacity:0.3;"></span></label></div>';
					html += '<div class="dplayer-comment-setting-type"><div class="dplayer-comment-setting-title">字幕位置</div><label><input type="radio" name="dplayer-danmaku-type-1" value="1"><span>上移</span></label><label><input type="radio" name="dplayer-danmaku-type-1" value="0" checked=""><span>默认</span></label><label><input type="radio" name="dplayer-danmaku-type-1" value="2"><span>下移</span></label></div>';
					html += '<div class="dplayer-comment-setting-type"><div class="dplayer-comment-setting-title">字幕大小</div><label><input type="radio" name="dplayer-danmaku-type-1" value="1"><span>加大</span></label><label><input type="radio" name="dplayer-danmaku-type-1" value="0"><span>默认</span></label><label><input type="radio" name="dplayer-danmaku-type-1" value="2"><span>减小</span></label></div>';
					html += '<div class="dplayer-comment-setting-type"><div class="dplayer-comment-setting-title">字幕偏移<span class="offset-text" style="border: 0px;width: 58px;"></span>偏移量 <input type="number" class="offset-value" style="height: 14px;width: 42px;font-size: 13px;border: 1px solid #fff;border-radius: 3px;color: black;line-height: normal;text-align: center;" step="1" min="1"></div><label><input type="radio" name="dplayer-danmaku-type-1" value="1"><span>前移</span></label><label><input type="radio" name="dplayer-danmaku-type-1" value="0"><span>默认</span></label><label><input type="radio" name="dplayer-danmaku-type-1" value="2"><span>后移</span></label></div>';
					html += '<div class="dplayer-comment-setting-type"><div class="dplayer-comment-setting-title">更多字幕</div><label><input type="radio" name="dplayer-danmaku-type-1" value="1"><span>本地上传</span></label><label><input type="radio" name="dplayer-danmaku-type-1" value="0"><span>在线搜索</span></label><label><input type="radio" name="dplayer-danmaku-type-1" value="2"><span>清空字幕</span></label></div>';
					html += '</div></div>';
					$(".dplayer-controller").append(html);
					subSetBox = $(".subtitle-setting-box");
					var colortxt = $(".color-value");
					var color1 = await sub.getItem("dplayer-subtitle-color")||"#ffffff";
					colortxt.val( color1)
					colortxt.on('input propertychange',async function(e) {
						var color = colortxt.val();
						color = color.replace(/[^#0-9a-fA-F]/g, "");
						color = color.replace(/^[0-9a-fA-F]/g, "");
						color = color.replace("#", "$@$").replace(/\#/g, "").replace("$@$", "#");
						color = color.replace(/^#([0-9a-fA-F]{3,6}).*$/, '#$1');

						colortxt.val(color);
						if (color1 != color) {
							await sub.setItem("dplayer-subtitle-color", color);
							$(".dplayer-subtitle").css("color", color);
						}
					});
					var txt = $(".offset-value");
					txt.val("5");
					txt.on('input propertychange', function(e) {
						var text = txt.val().replace(/[^\d]/g, "");
						txt.val(text);
					});
					var $input = $(".subtitle-setting-box .dplayer-comment-setting-color input[type='radio']");
					$input.each(function(index, element) {
						if($(this).val() == color1){
							$(this).next().css("opacity", "1");
							return false;
						};
					});
				}
				
				$input.on("click",async function() {
					var color = this.value;
					if ( await sub.getItem("dplayer-subtitle-color") != color) {
						 await sub.setItem("dplayer-subtitle-color", color);
						$(".dplayer-subtitle").css("color", color);
					}
					colortxt.val(color);
					$(".subtitle-color").css("opacity", "0.3");
					$(this).next().css("opacity", "1");
				});
				$(".subtitle-setting-box .dplayer-comment-setting-type input[type='radio']").on("click",async function() {
					var value = this.value;
					var $this = $(this), $name = $this.parent().parent().children(":first").text();
					if ($name == "字幕位置") {
						var bottom = Number(await sub.getItem("dplayer-subtitle-bottom") || 1);
						value == "1" ? bottom += 1 : value == "2" ? bottom -= 1 : bottom = 5;
						await sub.setItem("dplayer-subtitle-bottom", bottom);
						$(".dplayer-subtitle").css("bottom", bottom + "%");
					}else if ($name == "字幕大小") {
						var fontSize = Number(await sub.getItem("dplayer-subtitle-fontSize") || 4);
						value == "1" ? fontSize += .1 : value == "2" ? fontSize -= .1 : fontSize = 4;
						await sub.setItem("dplayer-subtitle-fontSize", fontSize);
						$(".dplayer-subtitle").css("font-size", fontSize + "vh");
					}else if ($name.includes("字幕偏移")){
						var offsettime = await sub.getItem("dplayer-subtitle-offset") || 0;
						var offsetvalue = Number($(".offset-value").val()) || 5;
						value == "1" ? offsettime -= offsetvalue : value == "2" ? offsettime += offsetvalue : offsettime = 0;
						offsettime == 0 ? $(".offset-text").text("") : $(".offset-text").text("["+ offsettime +"s]");
						await sub.setItem("dplayer-subtitle-offset",offsettime)
						const pid = GM_getValue('currentVideoPid');
						const subList = await sub.getItem(`video_${pid}`)||[];
						Xplayer.subtitleOffset(subList,true);
					}else if ($name == "更多字幕") {
						if (value == "0") {
							var x = confirm('请到字幕网站查找该视频字幕文件，下载后再本地上传到Dplayer。\n是否立即打开SubHD字幕网？');
							if (x){
								var title=document.title.replace(/_/g,'&nbsp;').replace(/\.\w{2,4}$/g,'');
								title = encodeURIComponent(title);
								GM_openInTab(`https://subhd.tv/search/${title}`,false);
							};
						}else if (value == "1") {
							if ($("#addsubtitle").length == 0) {
								$("body").append('<input id="addsubtitle" type="file" accept=".srt,.ass,.ssa" style="display: none;">');

							}
							$("#addsubtitle").click();
							$this.next().text("请等待...");
							setTimeout (function () {
								$this.next().text("本地上传")
							}, 5000);
						}else if (value == "2") {
							var x = confirm('是否立即清空本视频字幕缓存？\n注意：脚本每隔7天自动清除所有视频字幕缓存');
							if (x){
								const pid = GM_getValue('currentVideoPid');
								sub.removeItem(`video_${pid}`).then(function() {
									alert('本视频字幕缓存已清空，内置字幕会在刷新页面重新添加');
								})
							};

						}
					}
				});
			},

			selectSubtitles:async function (subList) {

				if ($(".dplayer-subtitle-btn .dplayer-quality-mask").length) {
					$(".dplayer-subtitle-btn .dplayer-quality-mask").remove();
				};
				
				var textTracks = dp.video.textTracks;
				if (!subList || subList.length==0) {
					$('.dplayer-subtitle-btn').hide();
					dp.subtitle.hide();
					return;
				};

				const pid = GM_getValue('currentVideoPid');
				var subbtn = $(".dplayer-subtitle-btn").addClass("dplayer-quality");

				let eleSub ='';
				for(var i = 0; i < subList.length; i++) {
					eleSub += '<div class="dplayer-quality-item subtitle-item" data-index1="'+ i +'">'+ subList[i].label +'</div>';
				};

				var html = '<div class="dplayer-quality-mask"><div class="dplayer-quality-list subtitle-select"> '+ eleSub +'</div></div>'
				subbtn.append(html);

				$(".subtitle-select .subtitle-item").off("click").on("click",function() {
					if ($(this).css("opacity") == 0.5) return;

					$(this).css("opacity", 0.5);
					$(this).siblings().css("opacity", 1);
					var index = $(this).attr("data-index1");

					var subitem = subList[index];
					if (subitem && subitem.sarr && subitem.sarr.length) {

						textTracks[0].mode = "hidden";
						for(var i = textTracks[0].cues.length - 1; i >= 0; i--) {
							textTracks[0].removeCue(textTracks[0].cues[i]);
						};

						subitem.sarr.forEach(function (item) {

							/<b>.*<\/b>/.test(item.text) || (item.text = item.text.split(/\r?\n/).map((item) => `<b>${item}</b>`).join("\n"));
							var textTrackCue = new VTTCue(item.startTime, item.endTime, item.text);
							textTrackCue.id = item.index;
							textTracks[0] && textTracks[0].addCue(textTrackCue);
							
						});

						textTracks[0].mode = "showing";
						$('.dplayer-subtitle-btn').show();
						dp.subtitle.show();

						subList[0].defaultSubtitleIndex = index
						sub.setItem(`video_${pid}`,subList);
						console.log('字幕加载成功');
						dp.notice('字幕加载成功', 3000);
					};
				});

				var currentTime = Date.now();
				var newSubIndex = -1;

				for(var i = 0; i < subList.length; i++) {
					if(subList[i].time && (currentTime - subList[i].time) <= 3000) {
						newSubIndex = i;
						break;
					};
				};

				setTimeout(() => {
					var index0 = newSubIndex !== -1 ? newSubIndex : (subList[0].defaultSubtitleIndex || 0);
					index0 >=0 && $(".subtitle-select .subtitle-item").eq(index0).click();
				}, 100);
			},

			subtitleStyle:async function () {

				const style = dp.subtitle.container.style

				const bottom = await sub.getItem("dplayer-subtitle-bottom")
				, color = await sub.getItem("dplayer-subtitle-color")
				, fontSize = await sub.getItem("dplayer-subtitle-fontSize");

				style.fontSize == fontSize + "vh" || (style.fontSize = fontSize + "vh");
				style.bottom == bottom + "%" || (style.bottom = bottom + "%");
				style.color == color || (style.color = color);

				style.textShadow || (style.textShadow = "1px 0 1px #000, 0 1px 1px #000, -1px 0 1px #000, 0 -1px 1px #000, 1px 1px 1px #000, -1px -1px 1px #000, 1px -1px 1px #000, -1px 1px 1px #000");

				if (!style.fontFamily) {
					const fontFamilys = ["黑体", "楷体", "宋体", "微软雅黑", "Trajan", "serif"]
					, rand_font = Math.floor(Math.random() * fontFamilys.length);
					style.fontFamily = fontFamilys[rand_font];
				}

			},

			subtitleOffset2:async function (subList,type) { 

				var offsetCache=await sub.getItem("dplayer-subtitle-offset")|| 0;
				if ((offsetCache == 0 && !type) || subList.length==0) return;
				const { video, subtitle } = dp;

				if (video.textTracks && video.textTracks[0]) {
					const track = video.textTracks[0];
					const cues = Array.from(track.cues); 
					let index = subList[0].defaultSubtitleIndex || 0;;

					if (!subList || !subList[index] || !subList[index].sarr) {
						console.warn('无效的字幕数据');
						return;
					}
					let sarr = subList[index].sarr;

					for (let i = 0; i < cues.length; i++) {
						const cue = cues[i];

						if (sarr[i]) {
							cue.startTime = clamp(sarr[i].startTime + offsetCache, 0, video.duration);
							cue.endTime = clamp(sarr[i].endTime + offsetCache, 0, video.duration);
						}
					}

					subtitle.init();
					dp.notice(`字幕偏移: ${offsetCache} 秒`);
				}else {

					await sub.setItem("dplayer-subtitle-offset",0);
				}

				function clamp(num, a, b) {
					return Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b));
				}
				
			},

			subtitleOffset:async function (subList,type) {

				var offsetCache = await sub.getItem("dplayer-subtitle-offset") || 0;

				if ((offsetCache == 0 && !type) || subList.length == 0) return;
				
				const { video, subtitle } = dp;

				if (video.textTracks && video.textTracks[0]) {
					const track = video.textTracks[0];
					const cues = Array.from(track.cues); 

					let index = subList[0].defaultSubtitleIndex || 0;

					if (!subList || !subList[index] || !subList[index].sarr) {
						console.warn('无效的字幕数据');
						return;
					}

					let sarr = subList[index].sarr;

					const playbackRate = video.playbackRate || 1.0;

					const adjustedOffsetCache = offsetCache / playbackRate;

					for (let i = 0; i < cues.length; i++) {
						const cue = cues[i];

						if (sarr[i]) {
							cue.startTime = clamp(sarr[i].startTime + adjustedOffsetCache, 0, video.duration);
							cue.endTime = clamp(sarr[i].endTime + adjustedOffsetCache, 0, video.duration);
						}
					}

					subtitle.init();
					dp.notice(`字幕偏移: ${offsetCache} 秒（播放速度: ${playbackRate}倍）`);
				} else {

					await sub.setItem("dplayer-subtitle-offset", 0);
				}

				function clamp(num, a, b) {
					return Math.min(Math.max(num, Math.min(a, b)), Math.max(a, b));
				}
			},

			getSubList:async function (type,callback) {

				const now = Date.now();
				const pid = GM_getValue('currentVideoPid');
				var currSubList = await sub.getItem(`video_${pid}`) || [];

				Xplayer.subtitleLocalFile(async function(subList) {
					if (Array.isArray(subList) && subList[0]) {
						currSubList = currSubList.concat(subList);
						currSubList = Xplayer.sortSubList(currSubList);

						await sub.setItem(`video_${pid}`,currSubList);
						console.log('本地字幕解析成功，待添加到字幕轨道');
						callback && callback(currSubList);
					};
				});

				Xplayer.getOnlineSubtitle(async function (subList) {
					if (Array.isArray(subList) && subList[0]) {
						currSubList = currSubList.concat(subList);
						currSubList = Xplayer.sortSubList(currSubList);

						await sub.setItem(`video_${pid}`,currSubList);
						console.log('在线字幕解析成功，待添加到字幕轨道');
						return callback && callback(currSubList);
					}else{
						if(currSubList.length===0){

							console.log('未获取到任何可用字幕');
							dp.subtitle.hide();
							callback && callback(false);
						}if (!type){
							dp.notice('未获取到新增的在线字幕', 3000);
							return;
						}else{
							callback && callback(currSubList);
						};
					};
				});
				
			},

			convertSrtToArray:function (srtText,showEnglish) {
				const blocks = srtText.split(/\n\s*\n/);
				let subtitleIndex = 0; 
				const result = [];

				const cleanSrtText = srtText
					.replace(/<font[^>]*>/g, '')
					.replace(/<\/font>/g, '')
					.replace(/{\\[^}]+}/g, '')
					.replace(/<\/?i>/g, '');
				const cleanBlocks = cleanSrtText.split(/\n\s*\n/);

				let formatType = null;
				const blockMap = {};

				cleanBlocks.forEach((block) => {
					if (!block.trim()) return;
					const lines = block.replace(/\r/g, '').trim().split("\n");
					const blockNum = lines[0].trim();
					if (blockNum && !isNaN(blockNum)) {
						if (blockMap[blockNum]) {
							formatType = 'format3';
						};
						blockMap[blockNum] = true;
					};
				});

				if (!formatType && cleanBlocks.length >= 20) {
					const cleanText = (text) => text.replace(/\{[^}]+\}/g, '').replace(/<\/?i>/g, '').trim();
					
					const block19 = cleanBlocks[18].replace(/\r/g, '').trim().split("\n").slice(2).join("\n");
					const block20 = cleanBlocks[19].replace(/\r/g, '').trim().split("\n").slice(2).join("\n");
					
					const text19 = cleanText(block19);
					const text20 = cleanText(block20);
					
					const isChinese19 = text19.match(/[\u4e00-\u9fa5]/);
					const isEnglish19 = text19.match(/[a-zA-Z]/);
					const isChinese20 = text20.match(/[\u4e00-\u9fa5]/);
					const isEnglish20 = text20.match(/[a-zA-Z]/);
					
					if (isChinese19 && !isEnglish19 && isChinese20 && !isEnglish20) {
						formatType = 'format1';
					} else if ((isChinese19 && isEnglish19) && (isChinese20 && isEnglish20)) {
						formatType = 'format2';
					} else {
						formatType = 'format1'; 
					};
				} else if (!formatType) {
					formatType = 'format1'; 
				};
				console.log('匹配srt字幕格式:', formatType);

				const processedBlocks = {};
				cleanBlocks.forEach((block, blockIndex) => {
					if (!block.trim()) return;
					
					const cleanBlock = block.replace(/\r/g, '').trim();
					const lines = cleanBlock.split("\n");
					const blockNum = lines[0].trim();

					let timeLineIndex = lines.findIndex(line => 
						line.match(/^\d{2}:\d{2}:\d{2}[,\.]\d{3}\s*-->\s*\d{2}:\d{2}:\d{2}[,\.]\d{3}$/)
					);
					
					if (timeLineIndex === -1) {
						timeLineIndex = lines[0].match(/^\d+$/) ? 1 : 0;
					};
					
					if (timeLineIndex >= lines.length) return;
					
					const timecode = lines[timeLineIndex].replace(/,/g, ".").replace(/\s+/g, " ");
					const [startTimeStr, endTimeStr] = timecode.split(" --> ");
					const startTime = Xplayer.parseTimestamp(startTimeStr);
					const endTime = Xplayer.parseTimestamp(endTimeStr);
					
					const textLines = lines.slice(timeLineIndex + 1).map(line => line.trim()).filter(line => line);
					const cleanText = textLines.join("\n").replace(/\{[^}]+\}/g, '').replace(/<\/?i>/g, '').trim();

					if (formatType === 'format3') {
						if (!processedBlocks[blockNum]) {
							processedBlocks[blockNum] = {
								startTime,
								endTime,
								chinese: '',
								english: ''
							};
						};
						
						const isChinese = cleanText.match(/[\u4e00-\u9fa5]/);
						if (isChinese) {
							processedBlocks[blockNum].chinese = cleanText;
						} else {
							processedBlocks[blockNum].english = cleanText;
						};
					} else {
						let displayText = '';
						if (formatType === 'format1') {
							displayText = cleanText;
						} else if (formatType === 'format2') {
							if (showEnglish) {
								const chinesePart = cleanText.split('\n')[0];
								const englishPart = cleanText.split('\n').slice(1).join('\n');
								displayText = chinesePart;
								if (englishPart) displayText += `\n${englishPart}`;
							} else {
								displayText = cleanText.split('\n')[0];
							};
						};
						
						if (displayText) {
							result.push({
								index: subtitleIndex,
								startTime,
								endTime,
								text: displayText
							});
							subtitleIndex++; 
						};
					};
				});

				if (formatType === 'format3') {
					Object.values(processedBlocks).forEach(block => {
						let displayText = '';
						if (showEnglish && block.english) {
							displayText = block.chinese ? `${block.chinese}\n${block.english}` : block.english;
						} else {
							displayText = block.chinese;
						}
						
						if (displayText) {
							result.push({
								index: subtitleIndex,
								startTime: block.startTime,
								endTime: block.endTime,
								text: displayText
							});
							subtitleIndex++; 
						};
					});
				};
				
				return result;
			},

			convertAssToArray:function (assText,showEnglish){
				const lines = assText.split('\n');
				let inEventsSection = false;
				let subtitleIndex = 0; 
				const result = [];
				
				function cleanText(text) {
					return text.replace(/\{[^}]+\}/g, '')  
						.replace(/<\/?i>/g, '')           
						.replace(/\\N/g, '\n')            
						.replace(/\\h/g, ' ')            
						.replace(/-/g, '')               
						.trim();                         
				};

				let formatType = null;
				const dialogueLines = [];
				for (const line of lines) {
					if (line.startsWith('Dialogue:')) {
						const parts = line.substring(9).split(',');
						if (parts.length >= 10) {
							let text = parts.slice(9).join(',').trim();
							dialogueLines.push(cleanText(text).replace(/\n/g, ' '));
							if (dialogueLines.length >= 201) break;
						};
					};
				};

				if (dialogueLines.length >= 201) {
					const line199 = dialogueLines[199];
					const line200 = dialogueLines[200];
					
					const isChinese199 = line199.match(/[\u4e00-\u9fa5]/);
					const isEnglish199 = line199.match(/[a-zA-Z]/);
					const isChinese200 = line200.match(/[\u4e00-\u9fa5]/);
					const isEnglish200 = line200.match(/[a-zA-Z]/);
					
					if (isChinese199 && !isEnglish199 && isChinese200 && !isEnglish200) {
						formatType = 'format1'; 
					} else if ((isChinese199 && isEnglish199) && (isChinese200 && isEnglish200)) {
						formatType = 'format2'; 
					} else if ((isChinese199 && !isEnglish199 && isEnglish200 && !isChinese200) || 
							  (isChinese200 && !isEnglish200 && isEnglish199 && !isChinese199)) {
						formatType = 'format3'; 
					} else{
						formatType = 'format1'; 
					};
				};

				function formatTimeToSeconds(assTime) {
					const [hms, ms] = assTime.split('.');
					const [h, m, s] = hms.split(':');
					return (parseInt(h) * 3600) + (parseInt(m) * 60) + parseFloat(s + '.' + (ms || '00'));
				};

				const subtitlePairs = {};
				for (const line of lines) {
					if (!line.trim() || line.startsWith(';')) continue;
					
					if (line.startsWith('[V4 Styles]') || line.startsWith('[V4+ Styles]') || line.startsWith('[Events]')) {
						inEventsSection = true;
						continue;
					};
					
					if (!inEventsSection) continue;
					if (line.startsWith('Dialogue:')) {
						const parts = line.substring(9).split(',');
						if (parts.length < 10) continue;

						const startTime = Xplayer.parseTimestamp(parts[1].trim().replace('.', ','));
						const endTime = Xplayer.parseTimestamp(parts[2].trim().replace('.', ','));
						let text = parts.slice(9).join(',').trim();
						
						const isChinese = text.match(/[\u4e00-\u9fa5]/);

						if (formatType === 'format1') {

							let displayText = cleanText(text.split('\\N')[0]);
							if (displayText) {
								result.push({
									index: subtitleIndex++,
									startTime: startTime,
									endTime: endTime,
									text: displayText
								});
							};
						} else if (formatType === 'format2') {

							if (!isChinese && !showEnglish) continue;
							
							let displayText = '';
							if (showEnglish) {
								const chinesePart = text.split('\\N')[0];
								const englishMatch = text.match(/\\N(\{\S+\})*([^\\]*)/);
								let chinese = cleanText(chinesePart);
								let english = englishMatch ? cleanText(englishMatch[2]) : '';
								
								displayText = chinese;
								if (english) displayText += `\n${english}`;
							} else {
								displayText = cleanText(text.split('\\N')[0]);
							};
							
							if (displayText) {
								result.push({
									index: subtitleIndex++,
									startTime: startTime,
									endTime: endTime,
									text: displayText
								});
							};
						} else if (formatType === 'format3') {

							const timeKey = `${startTime}-${endTime}`;
							if (!subtitlePairs[timeKey]) {
								subtitlePairs[timeKey] = {};
							};
							
							if (isChinese) {
								subtitlePairs[timeKey].chinese = cleanText(text);
							} else {
								subtitlePairs[timeKey].english = cleanText(text);
							};
							
							if (subtitlePairs[timeKey].chinese && subtitlePairs[timeKey].english) {
								let displayText = '';
								if (showEnglish) {
									displayText = `${subtitlePairs[timeKey].chinese}\n${subtitlePairs[timeKey].english}`;
								} else {
									displayText = subtitlePairs[timeKey].chinese;
								};
								
								result.push({
									index: subtitleIndex++,
									startTime: startTime,
									endTime: endTime,
									text: displayText
								});
							};
						};
					};
				};

				return result;
			},

			fetchSubtitleData:function (pid){
			  return new Promise(async function(resolve,reject){

					
					var subList = await sub.getItem(`video_${pid}`)||[];
					var href = 'https://115vod.com/webapi/movies/subtitle?pickcode='+pid;
					AjaxCall(href,async function(error,htmlTxt){
						if(error){
							console.log('网络错误，获取在线字幕失败！');
							resolve(false);
							return;
						};
						var json = JSON.parse(htmlTxt.replace(/\[\\u5185\\u7f6e\\u5b57\\u5e55\]/g,''));
						if(!json.state || htmlTxt.indexOf('title') == -1){
							resolve(false);
							return;
						};

						if(json.data.autoload && json.data.autoload.url){
							var targetSubtitle = json.data.autoload;
							var temp = await clipper.fetch(targetSubtitle.url,3);
							var buffer = new Uint8Array(temp);
							var md = forge.md.md5.create();
							md.update(forge.util.createBuffer(buffer));
							var currentMd5 = md.digest().toHex();
							console.log('文件md5值:', currentMd5);

							if(subList.some(item => item.md5 === currentMd5)){
								console.log('md5值匹配成功，跳过加载在线字幕1');
								resolve(false);
								return;
							}

							AjaxCall(targetSubtitle.url,function(error,subtitleText){
								if(error){
									console.log('获取字幕文本失败！');

									findSubtitleByTitle(json);
									return;
								};

								if(subtitleText.match(/[\u4e00-\u9fa5]/)){
									resolve({
										text: subtitleText,
										type: targetSubtitle.type,
										title: targetSubtitle.title,
										md5: currentMd5,
										url: targetSubtitle.url
									});
									console.log('默认字幕链接：\n'+targetSubtitle.title+'\n'+targetSubtitle.url);
								}else{

									console.log('默认字幕不包含中文，查找其他字幕');
									findSubtitleByTitle(json);
								};
							});
							return;
						};

						findSubtitleByTitle(json);

						async function findSubtitleByTitle(json){
							var targetSubtitle = null;
							var autoloadUrl = json.data.autoload?.url || '';
							var filteredList = json.data.list.filter(item => item.url !== autoloadUrl); 
							
							for (var i=0;i<filteredList.length;i++){
								if(filteredList[i].title.match(/简体|chs|zh-cn|simplified/i)){
									targetSubtitle = filteredList[i];
									break;
								};
							};
							
							if(!targetSubtitle){

								findChineseSubtitle(filteredList);
								return;
							};

							var temp = await clipper.fetch(targetSubtitle.url,3);
							var buffer = new Uint8Array(temp);
							var md = forge.md.md5.create();
							md.update(forge.util.createBuffer(buffer));
							var currentMd5 = md.digest().toHex();

							if(subList.some(item => item.md5 === currentMd5)){
								console.log('md5值匹配成功，跳过加载在线字幕1');
								resolve(false);
								return;
							};

							AjaxCall(targetSubtitle.url,function(error,subtitleText){
								if(error){
									console.log('获取字幕文本失败！');

									findChineseSubtitle(filteredList);
									return;
								};
								resolve({
									text: subtitleText,
									type: targetSubtitle.type,
									title: targetSubtitle.title,
									md5: currentMd5,
									url: targetSubtitle.url
								});
								console.log('在线字幕链接1：\n'+targetSubtitle.title+'\n'+targetSubtitle.url);
							});
						};

						async function findChineseSubtitle(subtitles){
							for(let i=0; i<subtitles.length; i++){
								var subtitle = subtitles[i];
								try {
									var text = await new Promise((resolve, reject) => {
										AjaxCall(subtitle.url, function(error, text){
											if(error) reject(error);
											else resolve(text);
										});
									});
									
									if(text.match(/[\u4e00-\u9fa5]/)){

										var temp = await clipper.fetch(subtitle.url,3);
										var buffer = new Uint8Array(temp);
										var md = forge.md.md5.create();
										md.update(forge.util.createBuffer(buffer));
										var currentMd5 = md.digest().toHex();

										if(subList.some(item => item.md5 === currentMd5)){
											console.log('md5值匹配成功，跳过加载在线字幕2');
											resolve(false);
											return;
										};
										resolve({
											text: text,
											type: subtitle.type,
											title: subtitle.title,
											md5: currentMd5,
											url: subtitle.url
										});
										console.log('在线字幕链接2：\n'+subtitle.title+'\n'+subtitle.url);
										return;
									};
								}catch(e){
									console.log('获取字幕失败:', e);
								};
							};

							resolve(false);
						};
					});
				});
			},

			getOnlineSubtitle:async function (callback) {
				const showEnglish = G.get('show_English');
				const pid = GM_getValue('currentVideoPid');
				const subtitleData = await Xplayer.fetchSubtitleData(pid);
				if (subtitleData) {
					const text = subtitleData.text;
					if (subtitleData.type=='srt'){
						var subtitleArray = Xplayer.convertSrtToArray(text,showEnglish);
					}else if (subtitleData.type=='ass'||subtitleData.type=='ssa'){
						var subtitleArray = Xplayer.convertAssToArray(text,showEnglish);
					}else {
						return false;
					};
					if (!Array.isArray(subtitleArray)) {
						console.log('在线字幕解析失败');
						callback && callback("");
					};
					var languages = Xplayer.languageAnalysis(subtitleArray);
					console.log('该在线字幕语言为：'+languages[1]);
					var subtitle = {
						language : languages[0],
						label : languages[1],
						md5 : subtitleData.md5,
						time : Date.now(),
						sarr : subtitleArray
					};
					callback && callback([subtitle]);
				}else{
					console.log('在线字幕添加失败,无效或无中文字幕');

					callback && callback("");
				};
			},

			uploadByFile:function(herf,key1,file,buffer){
				offline.getData(herf,$.param(key1)).then(function(json){
					console.log('字幕OSS上传凭证:');
					console.log(json);
					if(json.accessid){
						var uint8Array = new Uint8Array(buffer);
						var blob = new Blob([uint8Array], {type: file.type || 'application/octet-stream'});
						const params = {
							name: file.name,
							key: json.object,
							policy: json.policy,
							OSSAccessKeyId: json.accessid,
							success_action_status: 200,
							callback: json.callback,
							signature: json.signature,
							file: blob  
						};

						const formData = new FormData();
						for (const key in params) {
							formData.append(key, params[key]);
						};
						
						GM_xmlhttpRequest({
							method: 'POST',
							data: formData,
							url: json.host,
							responseType: 'json',
							headers: {
								'User-Agent': UA,
								'Origin': 'https://115.com'
							},
							onload: function(result){
								console.log('字幕上传网盘结果:');
								console.log(result.response);
								if (result.responseText.indexOf('xml')!= -1)return;
								if(result.response.state){
									dp.notice("字幕上传网盘成功", 3000);
								} else {
									dp.notice("网络异常,上传网盘失败", 3000);
								};
							},
							onerror: function(error){
								dp.notice("上传网盘出错: " + error, 3000);
							},
						});
					};
				});
					
			},

			localFileForText:function (callback) {
				$(document).on("change", "#addsubtitle", function(e) {
					if (this.files.length) {
						var file = this.files[0];
						var file_ext = file.name.split(".").pop().toLowerCase();

						var sexts = ["srt","ass","ssa"];
						if (!(file_ext && sexts.includes(file_ext))) {
							dp.notice('暂不支持此类型文件', 3000);
							return callback && callback("");
						}
						var reader = new FileReader();
						reader.onload = async function(e) {

							var buffer = e.target.result;
							var md5 = forge.md.md5.create();
							md5.update(forge.util.createBuffer(buffer));
							var currentMd5 = md5.digest().toHex();
							console.log('文件md5值:'+ currentMd5); 
							const pid = GM_getValue('currentVideoPid');
							var subList = await sub.getItem(`video_${pid}`)||[];

							if (subList && subList.length){
								for(var i = 0; i < subList.length; i++) {
									if(subList[i].md5 === currentMd5) {
										dp.notice('添加无效，字幕已存在', 3000);
										return;
									}
								}
							};

							const m3u8Info = await storage.getItem('m3u8Info');
							const herf = "https://uplb.115.com/3.0/sampleinitupload.php";
							const key = {
										userid: GM_getValue('115ID'),
										filename: file.name,
										filesize: file.size,
										target: "U_1_" + m3u8Info.cid
									};
							Xplayer.uploadByFile(herf,key,file,buffer);

							reader.readAsText(file, 'UTF-8');
							reader.onload = function(e) {
								var result = e.target.result;
								if (result.indexOf("�") > -1) {
									reader.onload = function(e) {
										return callback && callback({type: file_ext,text: e.target.result, md5: currentMd5});
									};
									reader.readAsText(file, "GBK");
								}else if (result.indexOf("") > -1) {
									reader.onload = function(e) {
										return callback && callback({type: file_ext, text: e.target.result, md5: currentMd5});
									};
									reader.readAsText(file, "BIG5");
								}else {
									return callback && callback({type: file_ext, text: result, md5: currentMd5});
								}
							};
						};
						reader.onerror = function(e) {
							console.log('文件读取失败');
							return callback && callback("");
						};
						reader.readAsArrayBuffer(file);
					}
					this.value = "";
					event.target.value = "";
				});
			},

			subtitleLocalFile:function (callback) {
				Xplayer.localFileForText(function (subtitleData) {
					const showEnglish = G.get('show_English');
					if (subtitleData) {
						const text = subtitleData.text;
						if (subtitleData.type=='srt'){
							var subtitleArray = Xplayer.convertSrtToArray(text,showEnglish);
						}else if (subtitleData.type=='ass'||subtitleData.type=='ssa'){
							var subtitleArray = Xplayer.convertAssToArray(text,showEnglish);
						}else {
							return false;
						};
						if (!Array.isArray(subtitleArray)) {
							console.log('本地字幕解析失败');
							callback && callback("");
						};
						var languages = Xplayer.languageAnalysis(subtitleArray);
						console.log('该本地字幕语言为：'+languages[1]);
						var subtitle = {
							language : languages[0],
							label : languages[1],
							md5 : subtitleData.md5,
							time : Date.now(),
							sarr : subtitleArray
						};
						callback && callback([subtitle]);
					}else {
						dp.notice('本地字幕添加失败', 3000);
						callback && callback("");
					};
				});
			},

			parseTimestamp: function(e) {
				if (!e || typeof e !== 'string') {
					console.error('无效的时间戳格式:', e);
					return 0;
				}
				try {
					var t = e.split(":")
					, n = parseFloat(t.length > 0 ? t.pop().replace(/,/g, ".") : "00.000") || 0
					, r = parseFloat(t.length > 0 ? t.pop() : "00") || 0;
					return 3600 * (parseFloat(t.length > 0 ? t.pop() : "00") || 0) + 60 * r + n;
				} catch (err) {
					console.error('解析时间戳失败:', err);
					return 0;
				}
			},

			languageAnalysis:function (sarr) {

				var sampleCount = Math.min(10, sarr.length);
				var samples = [];
				for(var i=0; i<sampleCount; i++) {
					samples.push(sarr[Math.floor(i * sarr.length / sampleCount)].text);
				}

				var t = samples.join("").replace(/[<bi\/>\r?\n]*/g, "");

				var chineseCount = (t.match(/[\u4e00-\u9fa5]/g) || []).length;
				var englishCount = (t.match(/[a-zA-Z]/g) || []).length;
				var totalChars = t.length;

				if((t.match(/[\u3020-\u303F]|[\u3040-\u309F]|[\u30A0-\u30FF]|[\u31F0-\u31FF]/g) || []).length / totalChars > 0.03) {
					return ["jpn","日文字幕"];
				}

				if(chineseCount/totalChars > 0.2 && englishCount/totalChars > 0.2) {
					return ["chs_eng","中英双语"];
				};

				if(chineseCount/totalChars > 0.2) {

					const t2sConverter = OpenCC.Converter({ from: 'hk', to: 'cn' });

					function similarity(original, converted) {
						let sameCount = 0;
						const minLength = Math.min(original.length, converted.length);
						for (let i = 0; i < minLength; i++) {
							if (original[i] === converted[i]) sameCount++;
						}
						return sameCount / original.length;
					}

					const t2sScore = similarity(t, t2sConverter(t)); 

					if(t2sScore > 0.95) {

						return ["chs","简体中文"];
					} else {

						return ["cht","繁体中文"];
					};
				};

				if(englishCount/totalChars > 0.2) {
					return ["eng","英文字幕"];
				};

				return ["unknown","未知语言"];
			},

			sortSubList:function (subList) {

				var chsList = [], chtList = [], otherlist = []; 
				subList.forEach(function (item, index) {
					if(item.language === "chs") {
						chsList.push(item);
					} else if(item.language === "cht") {
						chtList.push(item);
					} else {
						otherlist.push(item);
					}
				});

				chsList.sort((a, b) => a.time - b.time);
				chsList.forEach((item, index) => {
					const sameLangItems = chsList.filter(i => i.language === item.language);
					if(sameLangItems.length > 1) {
						const hasNumberSuffix = /\d+$/.test(item.label);
						if(!hasNumberSuffix) {
							const itemIndex = sameLangItems.indexOf(item) + 1;
							item.label = `${item.label}${itemIndex}`;
						}
					}
				});

				chtList.sort((a, b) => a.time - b.time);
				chtList.forEach((item, index) => {
					const sameLangItems = chtList.filter(i => i.language === item.language);
					if(sameLangItems.length > 1) {
						const hasNumberSuffix = /\d+$/.test(item.label);
						if(!hasNumberSuffix) {
							const itemIndex = sameLangItems.indexOf(item) + 1;
							item.label = `${item.label}${itemIndex}`;
						}
					}
				});

				otherlist.sort((a, b) => a.time - b.time);
				otherlist.forEach((item, index) => {
					const sameLangItems = otherlist.filter(i => i.language === item.language);
					if(sameLangItems.length > 1) {
						const hasNumberSuffix = /\d+$/.test(item.label);
						if(!hasNumberSuffix) {
							const itemIndex = sameLangItems.indexOf(item) + 1;
							item.label = `${item.label}${itemIndex}`;
						}
					}
				});

				return chsList.concat(chtList, otherlist); 
			},	
		
		};
	}();
	
	const offline = function(){
		return {	
			getSign:function(key,save_name){
				return new Promise(function(resolve, reject){
					if (/^\w+=/.test(key)){
						resolve(key);
						return;
					};
					
					var UserID = GM_getValue('115ID') || '';
					var cid = G.get('diy_folder')? GM_getValue('offlineFolder'):'';
					var title = save_name? save_name:'';
					GM_xmlhttpRequest({
						method: 'GET',
						url: sign_url,
						responseType: 'json',
						headers: {
							"User-Agent": UA,
							Origin: "https://115.com",
						},
						onload: function(result){
							if (result.responseText.indexOf('html')!= -1) {
								toastr.error('请先登录115网盘账号！','离线任务添加失败。');
								setTimeout(function(){
									var a = confirm('立即打开115网盘登录页面？');
									if (a){
										GM_openInTab('https://115.com/?mode=login',false);
									};
								}, 3000);
								return;
							};
							var data = {
								uid: UserID,
								sign: result.response.sign,
								time: result.response.time,
								wp_path_id: cid,
								savepath: title
							};
							
							if($.isPlainObject(key)){
								var value=$.param($.extend(data,key));

							}else{
								var value=$.param(data)+`&url=${key}`;
							};
							resolve(value);
						},
						onerror: function(error){
							reject(error);
						},
					});
				});
			},

			getData:function(herf,key,save_name){
				return offline.getSign(key,save_name).then(function(value){
					return new Promise(function(resolve, reject){
						GM_xmlhttpRequest({
							method: 'POST',
							data: value,
							url: herf,
							responseType: 'json',
							headers: {

								"User-Agent": UA,
								"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
								"Accept": "application/json, text/javascript, */*; q=0.01",
								Origin: "https://115.com",
								"X-Requested-With": "XMLHttpRequest"
							},
							onload: function(result){
								resolve(result.response);
							},
							onerror: function(error){
								reject(error);
							},
						});

					});
				});
			},
	
			del:function(id){
				if(id == 0){
					alert('网盘根目录,不可删除！');
					return ;
				};
				var Link = 'http://115.com/?ct=lixian&ac=get_id';
				AjaxCall(Link,function(error,htmlTxt){
					var json = JSON.parse(htmlTxt);
					if(json.cid == id){
						alert('云下载（离线保存默认文件夹），不可删除！');
						return ;
					};
					
					var del_url ='https://webapi.115.com/rb/delete';
					var key = 'fid='+id;
					offline.getData(del_url,key).then(function(json){

						if(json.state){
							var a = confirm('删除成功，可从回收站还原。是否立即关闭本页面？');
							if (a){

								window.open('','_self');
								window.close();
							};
						} else {
							alert('删除失败:'+json.error);
						};
					});
				});
			},
			
			setStar:function(fid,n){
				var txt = {'1':'设置','0':'取消'}[n];
				var star_url ='https://webapi.115.com/files/star';
				var key = `file_id=${fid}&star=${n}`;
				offline.getData(star_url,key).then(function(json){

					json.state ? alert(txt+'星标成功！') : alert(txt+'星标失败:'+json.error);
				});
			},

			newName:function(fid,name){
				var suffix = name.match(/\.\w{2,4}$/)[0];
				var name2 = name.replace(suffix,'');
				var a = prompt('请输入新的文件名：\n（不包含后缀 '+suffix+'）',name2);
				if (a!=null && a!=""){
					var edit_url ='https://webapi.115.com/files/edit';
					var key = `fid=${fid}&file_name=${a+suffix}`;
					offline.getData(edit_url,key).then(function(json){
						console.log('重命名结果:');
						console.log(json);
						if(json.state) {
							alert('重命名成功！现文件名为：\n'+json.file_name);
						} else {
							alert('重命名失败！原因：'+json.error);
						};
					});
				};
			},
			
			search:function(name,cid,callback){
				if(cid === 0){
					console.log('不允许搜索根目录。');
					callback(false);						
					return;
				};
				var searchLink = 'https://webapi.115.com/files/search?cid='+cid+'&search_value=%2E&type=4&limit=50';

				console.log('搜索文件夹：',searchLink );
				AjaxCall(searchLink,function(error,htmlTxt){
					if (typeof htmlTxt == 'undefined'){
						console.log('搜索失败，无返回数据');
						callback(false);
						return;
					}
					var json = JSON.parse(htmlTxt);
					console.log('搜索结果：');
					console.log(json);

					if(!json.data || json.data.length === 0) {
						console.log('无搜索结果');
						callback(false);
						return;
					}
					if(json.folder.name == '云下载'){
						console.log('不允许搜索离线主目录。');
						callback(false);						
						return;
					};
					
					var validVideos = [];

					for(var i=0; i<json.data.length; i++){
						var item = json.data[i];

						if(item.s > 1024*1024*20 && item.play_long > 180){ 
							validVideos.push(item);
						}
					}

					validVideos.sort(function(a, b){
						return a.n.localeCompare(b.n);
					});

					var maxResults = Math.min(validVideos.length, 3);

					for(var i=0; i<maxResults; i++){
						var video = {
							name: name2(validVideos[i].n),
							pid: validVideos[i].pc,
							cid: validVideos[i].cid,
							fid: validVideos[i].fid,
							size: change(validVideos[i].s),
							sha: validVideos[i].sha,
							time: validVideos[i].play_long
						};
						callback(true, video, i+1, validVideos.length);
						console.log('搜索结果'+i+':'+validVideos[i].n);
					}
					
					if(validVideos.length == 0){
						console.log('无有效视频结果');
						callback(false);
					}
				});
			},
			
			check:function(link){
				if(document.hidden){
					GM_setValue('noTimeOut',true);
					toastr.options.timeOut = 0;
				}else{
					GM_setValue('noTimeOut',false);
					toastr.options.timeOut = 12000;
				};
				
				var c = 1;
				var retry = false;
				let searchedCids = {}; 
				var txt2 = '5秒后自动重试，请稍等。';

                if(Array.isArray(link)){
                    var batchLinks = link;
                    var batchMarkLink = batchLinks.map(url => ({[url]: url}));
                    var key = '';
					var lists_url2 = lists_url+'&page='+c;
                    offline.getData(lists_url2,key).then(function(json){
                        if(!json.state){
                            toastr.error('查询离线结果失败。','服务器错误！');
                            return;
                        }
                        
                        batchLinks.forEach((url, index) => {
                            var dataEl = searchTask(json, url);
                            if(!dataEl) return;
                            
                            var name = dataEl.del_path==''? dataEl.name:dataEl.del_path.slice(0,-1);
							var a_del = `&nbsp;&nbsp;<a target="_blank" class="delTask" data=${dataEl.info_hash} href="javascript:void(0);" style="cursor:pointer;color:blue;" title="删除该离线任务">删除任务</a>`;
							processResult(dataEl, name, batchMarkLink[index],a_del,index);
                        });
                    });
                    return;
                };
				
				function processResult(dataEl,name,markLink,a_del,index) {
					if (dataEl.status != -1) {
						if (dataEl.move == -1) {
							toastr.warning('空间不足,请到115扩容', '离线下载异常！');
							return;
						}
						
						var down_result = dataEl.percentDone.toFixed(0);
						var cid = dataEl.file_id || 0;
						
						if (down_result >= 99 && cid != 0) {
							var txt = `文件(夹)名：${name}，大小：${change(dataEl.size)}。`;
							resultMark(markLink,3);
							
							if (show_result && !retry && index<2) {
								toastr.success(txt+a_list+a_del, '离线下载已完成',{timeOut:5000});
							}
							if(searchedCids[cid]){ 
								console.log(`文件夹cid:${cid}已处理过，跳过搜索`);
								return;
							}
							if (G.get('open_search') && index<2) {
								searchedCids[cid] = true; 
								handleSearch(dataEl, cid);
							}
							
						} else if(show_result && index<2) {
							resultMark(markLink,4);
							var txt = `文件(夹)名：${name}，下载进度为：<span style="color:purple;">${down_result}%</span>。`;
							toastr.warning(txt+a_list+a_del, '离线下载中...');
						}
						return;
					} else if(show_result && index<2) {
						resultMark(markLink,4);
						if(dataEl.err == 10016) {
							var txt = '文件含违规内容，已自动拦截。';
						} else {
							var txt = '未知原因，请到115网盘查看。';
							var a_del = '';
						}
						toastr.error(txt+a_list+a_del,'离线下载失败！',{timeOut:8000});
					}
					return;
				}
				
				function handleSearch(dataEl, cid) {
					offline.search(dataEl.name,cid,function(search_result,video,j,num){
						if (search_result) {
							if (G.get('search_result')) {
								var videoTxt = JSON.stringify(video);
								var txt = `文件名：${video.name}，大小：${video.size}，时长：${tranTime(video.time)}。`;
								var h1 = `<br><a target="_blank" class="115play" data=${videoTxt} href="javascript:void(0);" style="cursor:pointer;color:blue;" title="播放该视频">播放</a>`;
								var h2 = `&nbsp;&nbsp;<a target="_blank" class="115down" data=${videoTxt} one="1" href="javascript:void(0);" style="cursor:pointer;color:blue;" title="下载该视频">下载</a>`;
								var h3 = `&nbsp;&nbsp;<a target="_blank" class="115del" data=${videoTxt} href="javascript:void(0);" style="cursor:pointer;color:blue;" title="删除该视频文件夹">删除</a>`;
								var h4 = `&nbsp;&nbsp;<a target="_blank" class="115newName" data=${videoTxt} href="javascript:void(0);" style="cursor:pointer;color:blue;" title="重命名该视频">重命名</a>`;
								var h5 = `&nbsp;&nbsp;<a target="_blank" class="openFolder" data=${cid} href="javascript:void(0);" style="cursor:pointer;color:blue;" title="查看所属文件列表">查看</a>`;
								toastr.success(txt+h1+h2+h3+h4+h5,`发现第 ${j} 个视频（共 ${num} 个）`);
							}
							
							if (G.get('open_Popup') && j==1){
								setTimeout(function(){
									var type = '115play';
									playData(video,type);
								}, 500);
							}
						} else {
							handleNoSearchResult(dataEl);
						}
					});
				}
				
				function handleNoSearchResult(dataEl) {
					if(dataEl.move == 2 || dataEl.move == 0 || dataEl.status == 0){
						var txt = '离线数据取回网盘中。';
					} else {
						var txt = '未发现任何视频文件。';
					}
					var h1 = `<br><a target="_blank" class="openFolder" data=${dataEl.file_id} href="javascript:void(0);" style="color:blue;" title="点击打开所属文件列表">打开文件列表</a>`;
					toastr.warning(txt+txt2+h1, '视频搜索无结果！');
					
					if (!retry){
						setTimeout(function(){
							retry = true;
							txt2='';
							toastr.clear();
							console.log('重试搜索结果:');
							add(retry,txt2);
						}, 5000);
					}
				}
				
				function add(retry,txt2){
					if(c == 6){
						console.log('离线结果查询异常。离线任务数量过多，请清空后再试。');
						toastr.warning('离线任务数量过多，请清空后再试。', '离线结果查询异常！');						
						return;
					}
					
					var key = '';
					var lists_url2 = lists_url+'&page='+c;
					console.log('离线任务数据地址：'+lists_url2);
					
					offline.getData(lists_url2,key).then(function(json){
						console.log('离线任务列表第'+c+'页:');
						console.log(json);
						
						if(!json.state){
							toastr.error('查询离线结果失败。','服务器错误！');
							return;
						}
						
						var dataEl = searchTask(json,link);
						if(!dataEl){
							console.log('第'+c+'页查询失败，无匹配数据');
							if(c == json.page_count) {
								console.log('离线链接对比异常，已搜索所有离线列表页面，无返回结果。');
								toastr.warning('搜索参数错误。', '离线结果查询异常！',{timeOut:5000});										
								return;
							}
							c++
							add();
							return;
						}
						
						var name = dataEl.del_path==''? dataEl.name:dataEl.del_path.slice(0,-1);
						var a_del = `&nbsp;&nbsp;<a target="_blank" class="delTask" data=${dataEl.info_hash} href="javascript:void(0);" style="color:blue;" title="删除该离线任务">删除任务</a>`;
						var markLink = [{'url':link}];
						if(!processResult(dataEl,name,markLink,a_del,0)) return;
					});
				}
				
				if(!Array.isArray(link)) add(retry,txt2);
			},
			
			addButton:function(){
				$('[href]').each(function(){
					var url = $(this).attr('href');
					var reg1 =/\.(torrent|rar|zip|7z|mp4|rmvb|mkv|avi)$/i;
					var $El = $(this).parent().filter('li,td,th,:header').find('[Searched]');
					
					if ( (!down_reg.test(url) && !reg1.test(url)) || $(this).is('[Searched]') || $El.length>1
						|| ($El.length=1 && url.indexOf($El.attr('Searched')) != -1)){

						return;	
					};

					if (down_reg.test(url)){
						$(this).attr('Searched',url.split(':')[0]);
					}else if(/torrent$/i.test(url)){
						$(this).attr('Searched','torrent');
					}else{
						$(this).attr('Searched','other');
					};
					
					var link = getRightUrl(url);
					if(repeat(link)){
						return;
					};
					
					$(this).css('display','inline-block');
					$(this).after('<img src="https://cdn.jsdelivr.net/gh/zxf10608/JavaScript/icon/115logo.ico" class="115offline" data-href='+link+' style="z-index:9123456789;display:inline-block;cursor:pointer;margin:0px 5px 2px;border-radius:50%;border:0px;vertical-align:middle;outline:none!important;padding:0px!important;height:20px!important;width:20px!important;left:0px!important;top:0px!important;" title="使用115网盘离线下载，右键复制地址\n'+link+'">');
				});
			},
			
			addLink:function(){
				$('a,button,span,li,[value]').each(function(){
					var reg1 =/[\/\.\?\*\+\-=_:&#@!%]{1}([a-f0-9]{40}|[a-z2-7]{32})(?!\w)/i;
					var reg2 =/[a-z]{40}|[a-z]{32}/i;
					
					if ($(this).next().addBack().is('[Searched],[href*="google"]') 

						|| $(this).find('img').length>0){			
						return;	
					};

					var url = getAttribute(this);
					if(url.length>0){
						for(var i=0;i<url.length;i++){
							if(down_reg.test(url[i]) || (reg1.test(url[i]) && !reg2.test(url[i].match(reg1)[1]))){

								
								if(down_reg.test(url[i])){
									var value = url[i].split(':')[0];
									var templink = url[i];
								}else{
									var value = 'magnet';
									var templink = 'magnet:?xt=urn:btih:' + url[i].match(reg1)[1];
								};
								
								var link = getRightUrl(templink);
								if(repeat(link)){
									return;
								};
								$(this).attr({'Searched':value,'target':'_blank','style':'display:inline-block;'});
								$(this).after('<img src="https://cdn.jsdelivr.net/gh/zxf10608/JavaScript/icon/115logo.ico" class="115offline" data-href='+link+' style="z-index:9123456789;display:inline-block;cursor:pointer;margin:0px 5px 2px;border-radius:50%;border:0px;vertical-align:middle;outline:none!important;padding:0px!important;height:20px!important;width:20px!important;left:0px!important;top:0px!important;" title="使用115网盘离线下载2，右键复制地址\n'+link+'">');
								return;
							};
						};
					};
				
				});
			},
			
			addSelect:function(){
				if($('.115offline').length<3) return;
			
				$('.115offline:not([Sed])').each(function(){
					$(this).attr('Sed',1);
					var url=$(this).data('href');
					$(this).after('<input type="checkbox" class="115select" value='+url+' title="长按shift键，连续选择" style="z-index:9123456789;display:inline-block;cursor:pointer;height:16px!important;width:16px!important;margin:0px 2px 1px;border-radius:50%;border:0px;vertical-align:middle;outline:none!important;padding:0px!important;left:0px!important;top:0px!important;" />');
				});
				
				var sel=$('.115offline').length>10 ? $('.115select:eq(-1),.115select:eq(0)'):$('.115select:eq(-1)');
				sel.each(function(){
					if($(this).is('[batched]')) return;
					$(this).attr('batched',1);
					$(this).after('<img src="https://cdn.jsdelivr.net/gh/zxf10608/JavaScript/icon/batch_down00.png" class="115offline_batch" style="z-index:9123456789;display:inline-block;cursor:pointer;margin:0px 1px 2px;border:0px;vertical-align:middle;outline:none!important;padding:0px!important;height:23px!important;width:23px!important;left:0px!important;top:0px!important;" title="使用115网盘批量离线下载所选地址，右键可全选等">');
					$(this).parent().css('overflow','visible');
				});
			},

		};
	}();
	
	$(document).ready(function(){
		$(window).resize(function(){

			if(localHref.indexOf('http://videotsgo.115.com') != -1 || localHref.indexOf('https://115.com/web/lixian/') != -1){	
				Xplayer.autobox();
			}else if(localHref.indexOf('https://115.com/') != -1){
				list_menu();
			};
		});
		
		$(document).on('visibilitychange click',function(e){
			if(e.type == 'click'){
				$('.115menu').hide();
				return;
			};
		
			var isHidden = e.target.hidden;
			if (localHref.match(/^https?:\/\/(115\.com\/web\/lixian)|(videotsgo\.115\.com)\/$/) != null && 

				G.get('Tab_ing') && !document.pictureInPictureElement){
				isHidden ? $('video')[0].pause():$('video')[0].play();

			};
			
			var noTimeOut=GM_getValue('noTimeOut') || '';
			if (isHidden){

			}else if(noTimeOut){
				GM_setValue('noTimeOut','');
				setTimeout(function(){ 
					toastr.clear();
				}, 12000);

			}else{

			};
		});            
		
		$('body').on('click mouseenter mouseleave','.openPiP',function(e){
			if(e.type == 'click'){
				enterPiP($('video')[0]);
			}else if(e.type == 'mouseenter'){
				$(this).css('opacity', 1);
			}else if(e.type == 'mouseleave'){
				$(this).css('opacity', 0.7);
			};	
			return false;
		});
		
		$('body').on('click','.transcode_show,.transcode_fast',function(){
			if (!clickOne($(this))) return;
			var video = JSON.parse($(this).attr('data'));
			if($(this).is('.transcode_show')){
				var link = 'https://115vod.com/?hls=1&pickcode='+video.pid;
				GM_openInTab(link,false);
			}else{
				transcod_fast(video);
			};
			return false;
		});
		
		$('body').on('click','.115play,.115origin,.Dp,.Local',function(){
			if (!clickOne($(this))) return;
			var type = $(this).attr('class').replace(/name\s?/g,'');
			if ($(this).is('[data]')){ 
				var video = JSON.parse($(this).attr('data'));
			}else{
				var $El = $(this).parents('li');
				var video = {};
				video['name'] = name2($El.attr('title'));
				video['pid'] = $El.attr('pick_code');
				video['cid'] = $El.attr('cid');
				video['fid'] = $El.attr('file_id');
				video['size'] = change($El.attr('file_size'));
				video['sha'] = $El.attr('sha1');
			};
			playData(video,type);
			return false;
		});
		
		$('body').on('click','.115down',function(){
			if (!clickOne($(this))) return;
			
			if ($(this).is('[data]')){ 
				var file = JSON.parse($(this).attr('data'));
				var pid = file.pid;
				var fid = file.fid;
				var sha = file.sha;
				var cid = file.cid || '';
				var name = file.name;
			}else{
				var $El = $(this).parents('li');
				var pid = $El.attr('pick_code');
				var fid = $El.attr('file_id');
				var sha = $El.attr('sha1');
				var cid = $El.attr('cate_id') || '';
				var name = $El.attr('title');
			};
			
			if(cid=='' || $(this).is('[one]')){
				var key={pc:pid,fid:fid};
				download(key).then(function(data){
					if(data[0]){

						GM_openInTab(data[0]);
						if(G.get('show_sha')){
							setTimeout(function(){
								prompt('文件下载中，校验码(SHA1)为：',sha);
							}, 1000);
						};
					}else{
						toastr.warning(data[1],'下载失败!');
					};
				});
			}else{
				folderList(cid,name,99).then(function(data){
					if(data[0]){
						batchList(data[0],true);
					};
				});
			};
			return false;
		});

		$('body').on('click','.delTask',function(){
			var key = {'hash[0]':$(this).attr('data')};
			offline.getData(task_del,key).then(function(json){
				console.log('删除离线任务结果:');
				console.log(json);
				json.state ? alert('离线任务删除成功！') : alert('离线任务删除失败:'+json.error);
			});
			return false;
		});

		$('body').on('click','.115extract',function(){
			if (!clickOne($(this))) return;
			
			if(self!=top){
				var $sed=$('li.selected');
			}else{
				var $sed=$('iframe[name="wangpan"]').contents().find('li.selected');
			};
			
			if($sed.length>30){
				toastr.warning('单次选中数量限 <span style="color:red;">2-30</span> 个。','批量操作无效！',{timeOut:6000});
				return;
			};
			
			var p_id=$sed.attr('p_id');
			var info = [];
			$sed.each(function(e){
				var name=$(this).attr('title');
				var cid1=$(this).attr('cate_id');
				info.push({cid:cid1,n:name});
			});
			
			var p_id2 = prompt('请输入目标文件夹的cid值：\n   ※ 获取cid值方法：打开需要保存到的网盘文件夹，复制地址栏中"cid="后面的一串数字，以"&"截止，如https://115.com/?cid=012345678912345678&...，cid值则为 012345678912345678。该项不填或填无效值则保存至当前文件夹。※'
					  ,p_id);
			if (/^(\d{17,19}|0)$/.test(p_id2)){
				p_id=p_id2;
				toastr.success('输入cid值成功，将保存至cid值为：'+p_id+' 的文件夹。');
			}else if(p_id2==''){
				toastr.success('未输入cid值，保存至当前文件夹。');
			}else if(p_id2==null){
				toastr.warning('批量提取操作已取消！');
				return;
			}else{
				toastr.warning('请重新点击输入，该值除根目录为 0 外，其他文件夹均为17至19位纯数字！','文件夹cid值无效！');
				return;
			};
			
			setTimeout(function(){
				extractList(p_id,info);
			},1000);
			
			return false;
		});		
		
		$('body').on('click','.115M3Ulist,.115down_batch',function(){
			if (!clickOne($(this))) return;
			
			var M3U=$(this).is('.115M3Ulist')? true:false;
			if(self!=top){
				var $sed=$('li.selected');
			}else{
				var $sed=$('iframe[name="wangpan"]').contents().find('li.selected');
			};
			
			if((!M3U && $sed.length<2) || $sed.length>50){
				toastr.warning('单次选中数量限 <span style="color:red;">2-50</span> 个。','批量操作无效！',{timeOut:6000});
				return;
			};
			
			var end=false;
			var info = [];
			$sed.each(function(){
				var pid=$(this).attr('pick_code');
				var fid=$(this).attr('file_id');
				var name=$(this).attr('title');
				var sha=$(this).attr('sha1');
				if(M3U && !$(this).is(':has(.duration):not([file_mode="4"])')){

					return;
				};
				
				if($(this).is('[file_type="0"]')){
					end=true;
					toastr.warning('文件夹暂不支持批量操作，请取消选中。','批量操作无效！',{timeOut:6000});
					return false;
				};
				info.push({pc:pid,fid:fid,n:name,sha:sha});
			});
			if(end)return;
			if(M3U&&info.length==0){
				toastr.warning('未选中任何视频文件。','批量操作无效！',{timeOut:6000});
				return;
			};
			
			var key={count:info.length,data:info};
			if (M3U){ 
				batchList(key);
			}else{
				batchList(key,true);
			};
			return false;
		});
		
		$('body').on('click','.115select',function(e){
			if(e.shiftKey){
				var first=$('.115select').index($('.115select:checked').first());
				var me=$('.115select').index($(this));
				var last=$('.115select').index($('.115select:checked').last());
				var Min = Math.min(first,me,last);
				var Max = Math.max(first,me,last);
				
				for(var i=Min;i<=Max;i++){
					$('.115select').eq(i).prop('checked',true);
				};
			};
		});
		
		$('body').on('click','.115del,.115newName',function(){
			if (!clickOne($(this))) return;
			
			var video = JSON.parse($(this).attr('data'));
			var title = video.name;
			var folderID = video.cid;
			var videoID = video.fid;
			if($(this).is('.115del')){
				var a = confirm('确认删除 '+title+' 视频所属文件夹？');
				if (a){
					offline.del(folderID);
				};
			}else{
				offline.newName(videoID,title);
			};
			return false;
		});
		
		$('body').on('click','.openList:not([click="1"]),.openFolder:not([click="1"])',function(){
			$(this).attr('click', '1');
			if($(this).is('.openList')){
				var txt='tab=offline';
			}else{
				var fID=$(this).attr('data');
				var txt='cid='+fID+'&offset=0';
			};
			GM_openInTab('https://115.com/?'+txt+'&mode=wangpan',false);
			return false;
		});
		
		$('body').on('contextmenu','.115offline,.115offline_batch',function(e){
			$('.115menu').css({left:e.pageX+'px',top:e.pageY+'px'});
			$('.115menu').show();
			var link=$(this).data('href') || '';
			if($(this).is('.115offline_batch')){
				$('.115menu [class^="right_menu0"]').show();
				$('.115menu [class^="right_menu1"]').hide();
			}else if(/^magnet/i.test(link)){
				$('.115menu').attr('data-href',link);
				$('.115menu [class^="right_menu1"]').show();
				$('.115menu [class^="right_menu0"]').hide();
			}else{
				$('.115menu').hide();
				GM_setClipboard(link);
				toastr.success('下载地址复制成功！'); 
			};
			return false; 
		});
		
		$('body').on('click','[class^="right_menu0"]',function(){
			if ($(this).is('.right_menu00')){
				$('.115select').prop('checked',true);
			}else if($(this).is('.right_menu01')){
				$('.115select').each(function(){
					if($(this).prop('checked')){
						$(this).prop('checked',false);
					}else{
						$(this).prop('checked',true);				
					};
				});
			}else{
				if($('.115select:checked').length==0){
					toastr.warning('复制失败，未选中任何链接！');
					return;
				};
				var urls = [];
				$('.115select:checked').each(function(){
					urls.push($(this).attr('value'));
				});
				GM_setClipboard(urls.join('\r\n'));
				toastr.success(urls.length+' 个下载地址复制成功！');
			};
			return false;
		});
		
		$('body').on('click','[class^="right_menu1"]',function(){
			var link=$(this).parents('.115menu').data('href');
			var hash=link.match(/[a-f0-9]{40}/i)[0].toUpperCase();
			if($(this).is('.right_menu11')){
				GM_setClipboard(link);
				toastr.success('下载地址复制成功！');
			}else if($(this).is('.right_menu12')){
				GM_openInTab(`https://itorrents.org/torrent/${hash}.torrent`,false);
			}else if($(this).is('.right_menu13')){
				GM_openInTab(`https://btcache.me/torrent/${hash}`,false);

			};
			return false;

		});
		
		$('body').on('click','.115offline',function(e){

			if (!clickOne($(this))) return;
			var link = $(this).data('href');
			var save_name ='';
			offline.getData(add_url,link,save_name).then(function(json){
				console.log('离线任务添加结果:');
				console.log(json);
				var errNum = json.errcode || json.error_code || '';
				var markLink=[{'url':link}];
				
				if(json.state){
					if (show_result){
					var txt = '10秒后显示离线结果。';
					}else{
						var txt = link
						if (G.get('open_List')){
							setTimeout(function(){
								GM_openInTab('https://115.com/?tab=offline&mode=wangpan',false);
							}, 2000);
						};
					};
					
					resultMark(markLink,1);
					toastr.info(txt,'离线任务添加成功。',{timeOut:10000});
					setTimeout(function(){
						offline.check(link);
					}, 10000);
				} else if (errNum == 10008){
					toastr.warning('任务已存在，无需重复添加。','离线任务添加无效!',{timeOut:5000});
					if (G.get('open_List')){
						setTimeout(function(){
							GM_openInTab('https://115.com/?tab=offline&mode=wangpan',false);
						}, 2000);
					};
					resultMark(markLink,1);
					offline.check(link,markLink);
				} else if (errNum == 911){
					toastr.warning('账号异常，请验证账号。','离线下载失败！',{timeOut:5000});
					setTimeout(function(){
						verify();
					}, 1000);
				} else {
					resultMark(markLink,2);
					toastr.warning(json.error_msg,'离线任务添加失败!',{timeOut:12000});
				};
				console.log('离线链接:'+link+' 添加结果:'+json.state+' 原因:' +json.error_msg);
			}, function(error) {
				toastr.error('服务器繁忙，请稍后再试。','离线任务添加异常!');
				console.log(error);
			});
			return false;
		});
		
		$('body').on('click','.115offline_batch',function(){
			var l=$('.115select:checked').length;
			if (l<10 && !clickOne($('.115offline_batch'))) return;
			if(l<2 || l>100){
				toastr.warning('单次选中数量限 <span style="color:red;">2-99</span> 个。','批量离线操作无效！',{timeOut:6000});
				return;
			}
			if(l>10){
				toastr.info('所选中地址较多，服务器需要较长时间响应，请稍等10s以上，未弹出结果前勿重复点击。','温馨提示。',{timeOut:10000});
				if(!clickOne($('.115offline_batch'),10000)) return;
			}
			
			var links = {};
			$('.115select:checked').each(function(e){
				links['url['+e+']']=$(this).attr('value');
			});
			
			offline.getData(add_urls,links).then(function(json){
				console.log('批量离线任务添加结果:', json);
				var errNum = json.errcode || json.error_code || '';
				
				if(!json.state){
					if(errNum == 911){
						toastr.warning('账号异常，请验证账号。','批量离线下载失败！',{timeOut:5000});
						setTimeout(verify, 1000);
					}else{
						toastr.warning(json.error_msg+a_list,'批量离线任务添加失败!',{timeOut:12000});
					}
					return;
				}
				
				var results = {
					success: json.result.filter(r=>r.state),
					exist: json.result.filter(r=>r.errcode==10008),
					fail: json.result.filter(r=>!r.state && r.errcode!=10008)
				};
				
				var msg = offline_batch_message(results, json.result.length);
				toastr[msg.type](msg.text, msg.title, {timeOut:10000});

				results.success.forEach(r=>resultMark([r],1));
				results.exist.forEach(r=>resultMark([r],1));
				results.fail.forEach(r=>resultMark([r],2));

				if(show_result && results.success.length + results.exist.length <= 20){
					var re = results.success.concat(results.exist);
					setTimeout(()=>{

						var urls = re.map(r => r.url);
						offline.check(urls);
					}, 10000);
				}
				
				if(results.fail.length!=json.result.length && G.get('open_List')){
					setTimeout(()=>GM_openInTab('https://115.com/?tab=offline&mode=wangpan',false), 2000);
				}
			}, function(error) {
				toastr.error('服务器繁忙，请稍后再试。','批量离线任务添加异常!');
				console.log(error);
			});
			
			return false;
		});

		if(G.get('offline_Down') && localHref.match(/115(vod)?\.com/) == null){
			right_menu();
			var time=300;
			if(G.get('fuzzy_find')){
				setTimeout(function(){
					offline.addLink();
				},time+100);
			};
			
			var time1 = setInterval(function(){
				offline.addButton();
			},time+200);
			
			setTimeout(function(){
				offline.addSelect();
				$('.mag1').remove();
				clearInterval(time1);
				if(localHref.match(/av/) != null){
					$('.movie td').removeAttr('onclick');
				};
			},time*10);
		};
	});
	
})();